pax_global_header 0000666 0000000 0000000 00000000064 15155455534 0014526 g ustar 00root root 0000000 0000000 52 comment=98d5f7a943a4e33821541e5f6347bef42bb96e26
mogest-prawn-svg-98d5f7a/ 0000775 0000000 0000000 00000000000 15155455534 0015377 5 ustar 00root root 0000000 0000000 mogest-prawn-svg-98d5f7a/.github/ 0000775 0000000 0000000 00000000000 15155455534 0016737 5 ustar 00root root 0000000 0000000 mogest-prawn-svg-98d5f7a/.github/workflows/ 0000775 0000000 0000000 00000000000 15155455534 0020774 5 ustar 00root root 0000000 0000000 mogest-prawn-svg-98d5f7a/.github/workflows/lint.yml 0000664 0000000 0000000 00000000514 15155455534 0022465 0 ustar 00root root 0000000 0000000 name: RuboCop
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3
bundler-cache: true
- name: Run RuboCop
run: bundle exec rubocop --parallel
mogest-prawn-svg-98d5f7a/.github/workflows/test.yml 0000664 0000000 0000000 00000001042 15155455534 0022473 0 ustar 00root root 0000000 0000000 name: test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
ruby: [2.7, '3.0', 3.1, 3.2, 3.3]
steps:
- uses: actions/checkout@v4
- name: Remove Gemfile.lock
run: rm -f Gemfile.lock
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true
ruby-version: ${{ matrix.ruby }}
- name: Run tests
run: bundle exec rake
env:
RUBYOPT: "--enable-frozen-string-literal"
mogest-prawn-svg-98d5f7a/.gitignore 0000664 0000000 0000000 00000000231 15155455534 0017363 0 ustar 00root root 0000000 0000000 .DS_Store
spec/sample_output/*.pdf
spec/sample_ttf/NotoSansJP-Regular.ttf
prawn-svg-*.gem
.rvmrc
.*.swp
.ruby-version
vendor/bundle/
.bundle/
.mise.toml
mogest-prawn-svg-98d5f7a/.rspec 0000664 0000000 0000000 00000000037 15155455534 0016514 0 ustar 00root root 0000000 0000000 --format documentation
--color
mogest-prawn-svg-98d5f7a/.rubocop.yml 0000664 0000000 0000000 00000002404 15155455534 0017651 0 ustar 00root root 0000000 0000000 inherit_from: .rubocop_todo.yml
AllCops:
TargetRubyVersion: 2.7
NewCops: enable
Metrics/AbcSize:
Enabled: false
Metrics/BlockLength:
Enabled: false
Metrics/ClassLength:
Enabled: false
Metrics/CyclomaticComplexity:
Enabled: false
Metrics/MethodLength:
Enabled: false
Metrics/ModuleLength:
Enabled: false
Metrics/PerceivedComplexity:
Enabled: false
Style/Documentation:
Enabled: false
Style/FrozenStringLiteralComment:
Enabled: false
Style/ClassAndModuleChildren:
Enabled: false
Style/GuardClause:
Enabled: false
Style/SymbolArray:
Enabled: false
Style/WordArray:
Enabled: false
Layout/HashAlignment:
EnforcedHashRocketStyle: table
EnforcedColonStyle: table
Layout/FirstArgumentIndentation:
EnforcedStyle: consistent
Layout/ArgumentAlignment:
EnforcedStyle: with_fixed_indentation
Lint/SuppressedException:
Enabled: false
Style/AccessorGrouping:
Enabled: false
Style/PerlBackrefs:
Enabled: false
Naming/MethodParameterName:
Enabled: false
Metrics/ParameterLists:
Enabled: false
Metrics/BlockNesting:
Enabled: false
Naming/VariableNumber:
Enabled: false
Lint/ConstantDefinitionInBlock:
Exclude:
- spec/**/*.rb
Style/GlobalVars:
Exclude:
- spec/**/*.rb
Style/MultilineBlockChain:
Enabled: false
mogest-prawn-svg-98d5f7a/.rubocop_todo.yml 0000664 0000000 0000000 00000003610 15155455534 0020676 0 ustar 00root root 0000000 0000000 # This configuration was generated by
# `rubocop --auto-gen-config`
# on 2024-06-16 03:19:10 UTC using RuboCop version 1.62.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.
# Offense count: 1
# Configuration parameters: AllowComments, AllowEmptyLambdas.
Lint/EmptyBlock:
Exclude:
- 'Rakefile'
# Offense count: 13
Lint/NonLocalExitFromIterator:
Exclude:
- 'lib/prawn/svg/css/selector_parser.rb'
- 'lib/prawn/svg/ttf.rb'
# Offense count: 1
Naming/AccessorMethodName:
Exclude:
- 'lib/prawn/svg/calculators/document_sizing.rb'
# Offense count: 1
# Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, CheckDefinitionPathHierarchyRoots, Regex, IgnoreExecutableScripts, AllowedAcronyms.
# CheckDefinitionPathHierarchyRoots: lib, spec, test, src
# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
Naming/FileName:
Exclude:
- 'lib/prawn-svg.rb'
# Offense count: 9
# This cop supports safe autocorrection (--autocorrect).
Style/IfUnlessModifier:
Exclude:
- 'lib/prawn/svg/css/selector_parser.rb'
- 'lib/prawn/svg/elements/base.rb'
- 'lib/prawn/svg/elements/gradient.rb'
- 'lib/prawn/svg/elements/root.rb'
- 'lib/prawn/svg/elements/text_component.rb'
- 'lib/prawn/svg/elements/use.rb'
- 'lib/prawn/svg/loaders/file.rb'
# Offense count: 28
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
# URISchemes: http, https
Layout/LineLength:
Max: 180
mogest-prawn-svg-98d5f7a/Gemfile 0000664 0000000 0000000 00000001067 15155455534 0016676 0 ustar 00root root 0000000 0000000 source 'http://rubygems.org'
# Declare your gem's dependencies in prawn-svg.gemspec.
# Bundler will treat runtime dependencies like base dependencies, and
# development dependencies will be added by default to the :development group.
gemspec
# Declare any dependencies that are still in development here instead of in
# your gemspec. These might include edge Rails or gems from your path or
# Git. Remember to move these dependencies to your gemspec before releasing
# your gem to rubygems.org.
gem 'rake', '~> 13.0'
gem 'rspec', '~> 3.0'
gem 'rubocop', '~> 1.64'
mogest-prawn-svg-98d5f7a/Gemfile.lock 0000664 0000000 0000000 00000003347 15155455534 0017630 0 ustar 00root root 0000000 0000000 PATH
remote: .
specs:
prawn-svg (0.40.0)
css_parser (~> 1.6)
matrix (~> 0.4.2)
prawn (>= 0.11.1, < 3)
rexml (>= 3.4.2, < 4)
GEM
remote: http://rubygems.org/
specs:
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
bigdecimal (3.1.8)
css_parser (1.17.1)
addressable
diff-lcs (1.5.1)
json (2.7.2)
language_server-protocol (3.17.0.3)
matrix (0.4.2)
parallel (1.25.1)
parser (3.3.3.0)
ast (~> 2.4.1)
racc
pdf-core (0.10.0)
prawn (2.5.0)
matrix (~> 0.4)
pdf-core (~> 0.10.0)
ttfunk (~> 1.8)
public_suffix (5.1.0)
racc (1.8.0)
rainbow (3.1.1)
rake (13.2.1)
regexp_parser (2.9.2)
rexml (3.4.4)
rspec (3.13.0)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.0)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-support (3.13.1)
rubocop (1.64.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.3)
parser (>= 3.3.1.0)
ruby-progressbar (1.13.0)
ttfunk (1.8.0)
bigdecimal (~> 3.1)
unicode-display_width (2.5.0)
PLATFORMS
ruby
DEPENDENCIES
prawn-svg!
rake (~> 13.0)
rspec (~> 3.0)
rubocop (~> 1.64)
BUNDLED WITH
2.5.1
mogest-prawn-svg-98d5f7a/LICENSE 0000664 0000000 0000000 00000002063 15155455534 0016405 0 ustar 00root root 0000000 0000000 The MIT License
Copyright 2010-2019 Roger Nesbitt
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.
mogest-prawn-svg-98d5f7a/README.md 0000664 0000000 0000000 00000021317 15155455534 0016662 0 ustar 00root root 0000000 0000000 # prawn-svg
[](https://badge.fury.io/rb/prawn-svg)

An SVG renderer for the [Prawn PDF library](https://github.com/prawnpdf/prawn).
This will take an SVG document as input and render it into your PDF, along with whatever else you build with Prawn.
prawn-svg is compatible with all versions of Prawn from 0.11.1 onwards, including the 1.x and 2.x series, although
you'll need version 2.2.0 onwards if you want color gradients. The minimum Ruby version required is 2.7.
## Using prawn-svg
```ruby
Prawn::Document.generate("test.pdf") do
svg ' '
end
```
prawn-svg will do something sensible if you call it with only an SVG document, but you can also
pass the following options to tailor its operation:
Option | Data type | Description
----------- | --------- | -----------
:at | [integer, integer] | Specify the location on the page you want the SVG to appear.
:position | :left, :center, :right, integer | If :at not specified, specifies the horizontal position to show the SVG. Defaults to :left.
:vposition | :top, :center, :bottom, integer | If :at not specified, specifies the vertical position to show the SVG. Defaults to current cursor position.
:width | integer | Desired width of the SVG. Defaults to horizontal space available.
:height | integer | Desired height of the SVG. Defaults to vertical space available.
:enable_web_requests | boolean | If true, prawn-svg will make http and https requests to fetch images, `@font-face` fonts, and external `` references. Defaults to true, but will **default to false** in the upcoming 1.0 release. It's recommended you explicitly set this option for now.
:enable_file_requests_with_root | string | If not nil, prawn-svg will serve `file:` URLs and relative paths from your local disk if the file is located under the specified directory. Required for `@font-face` fonts and external `` references loaded via file paths. It is very dangerous to specify the root path ("/") if you're not fully in control of your input SVG. Defaults to `nil` (off).
:cache_images | boolean | If true, prawn-svg will cache the result of all URL requests. Defaults to false.
:fallback_font_name | string | A font name which will override the default fallback font of Times-Roman. If this value is set to `nil`, prawn-svg will ignore a request for an unknown font and log a warning.
:color_mode | :rgb, :cmyk | Output color mode. Defaults to :rgb.
:language | string | BCP 47 language tag for `` `systemLanguage` matching. Defaults to `"en"`.
:log_warnings | boolean | If true, warnings that occur when parsing/rendering the SVG are output to stderr via `warn`. Use this when you're not getting the output you expect. Defaults to false.
## Examples
```ruby
# Render the logo contained in the file logo.svg at 100, 100 with a width of 300
svg IO.read("logo.svg"), at: [100, 100], width: 300
# Render the logo at the current Y cursor position, centered in the current bounding box
svg IO.read("logo.svg"), position: :center
# Render the logo at the current Y cursor position, and serve file: links relative to its directory
root_path = "/apps/myapp/current/images"
svg IO.read("#{root_path}/logo.svg"), enable_file_requests_with_root: root_path, log_warnings: true
```
## Supported features
prawn-svg supports almost all of the full SVG 1.1 specification:
- ``, ``, ``, ``, `` and ``
- ``
- ``, ``, `` and `` with attributes `x`, `y`, `dx`, `dy`, `rotate`, `textLength`,
`lengthAdjust`, and with extra properties `text-anchor`, `text-decoration`, `font`, `font-size`, `font-family`,
`font-weight`, `font-style`, `font-stretch`, `kerning`, `letter-spacing`, `word-spacing`, `dominant-baseline`, `alignment-baseline`, `baseline-shift`.
`` supports `href`/`xlink:href` and `startOffset`.
- ``, `` and ``
- `` with local and external references (e.g. `other.svg#elementId`), subject to `enable_web_requests` and `enable_file_requests_with_root`
- `
SVG
end
it 'is correctly converted to a call stack' do
element.process
expect(element.calls).to eq [
['fill_color', ['000000'], {}, []],
['transformation_matrix', [1, 0, 0, 1, 0, 0], {}, []],
['transformation_matrix', [1, 0, 0, 1, 0, 0], {}, []],
['save', [], {}, []], ['restore', [], {}, []],
['save', [], {}, []],
['fill_color', ['0000ff'], {}, []],
['fill', [], {}, [
['rectangle', [[0.0, 200.0], 10.0, 10.0], {}, []]
]],
['restore', [], {}, []],
['save', [], {}, []],
['fill_color', ['008000'], {}, []],
['fill', [], {}, [
['rectangle', [[10.0, 200.0], 10.0, 10.0], {}, []]
]],
['restore', [], {}, []],
['save', [], {}, []],
['fill_color', ['ff0000'], {}, []],
['fill', [], {}, [
['rectangle', [[20.0, 200.0], 10.0, 10.0], {}, []]
]],
['restore', [], {}, []],
['save', [], {}, []],
['fill_color', ['ffff00'], {}, []],
['fill', [], {}, [
['rectangle', [[30.0, 200.0], 10.0, 10.0], {}, []]
]],
['restore', [], {}, []]
]
end
end
context 'with option :position' do
let(:svg) { File.read("#{root}/spec/sample_svg/cubic01a.svg") }
it 'aligns the image as requested' do
Prawn::Document.generate("#{root}/spec/sample_output/_with_position.pdf") do |prawn|
width = prawn.bounds.width / 3
prawn.svg svg, width: width, position: :left, enable_web_requests: false
prawn.svg svg, width: width, position: :center, enable_web_requests: false
prawn.svg svg, width: width, position: :right, enable_web_requests: false
prawn.svg svg, width: width, position: 50, enable_web_requests: false
prawn.svg svg, width: width, enable_web_requests: false
end
end
end
context 'with option :vposition' do
let(:svg) { File.read("#{root}/spec/sample_svg/cubic01a.svg") }
it 'aligns the image as requested' do
Prawn::Document.generate("#{root}/spec/sample_output/_with_vposition.pdf") do |prawn|
width = prawn.bounds.width / 3
prawn.svg svg, width: width, position: :left, vposition: :bottom, enable_web_requests: false
prawn.svg svg, width: width, position: :center, vposition: :center, enable_web_requests: false
prawn.svg svg, width: width, position: :right, vposition: :top, enable_web_requests: false
prawn.svg svg, width: width, position: 50, vposition: 50, enable_web_requests: false
end
end
end
describe 'sample file rendering' do
files = Dir["#{root}/spec/sample_svg/*.svg"]
let(:ttf_directory) { Pathname.new(__dir__).join('sample_ttf') }
let(:noto_sans_ttf) { ttf_directory.join('NotoSansJP-Regular.ttf') }
before do
next if noto_sans_ttf.exist?
FileUtils.mkdir_p(noto_sans_ttf.dirname)
uri = URI('https://github.com/googlefonts/noto-cjk/raw/main/Sans/Variable/TTF/Subset/NotoSansJP-VF.ttf')
tempfile = uri.open
FileUtils.mv(tempfile.path, noto_sans_ttf)
end
it 'has at least 10 SVG sample files to test' do
files.length.should >= 10
end
files.each do |file|
it "renders the #{File.basename file} sample file without warnings or crashing" do
expect(Net::HTTP).to_not receive(:get)
warnings = nil
Prawn::Document.generate("#{root}/spec/sample_output/#{File.basename file}.pdf") do |prawn|
prawn.font_families.update(
'Noto Sans' => {
normal: noto_sans_ttf.to_s
}
)
r = prawn.svg File.read(file), at: [0, prawn.bounds.top], width: prawn.bounds.width,
enable_web_requests: false, enable_file_requests_with_root: File.dirname(__FILE__) do |doc|
doc.url_loader.add_to_cache(
'https://raw.githubusercontent.com/mogest/prawn-svg/master/spec/sample_images/mushroom-wide.jpg', File.read("#{root}/spec/sample_images/mushroom-wide.jpg")
)
doc.url_loader.add_to_cache(
'https://raw.githubusercontent.com/mogest/prawn-svg/master/spec/sample_images/mushroom-long.jpg', File.read("#{root}/spec/sample_images/mushroom-long.jpg")
)
end
warnings = r[:warnings].reject do |w|
(w =~ /Verdana/ && w =~ /is not a known font/) || w =~ /(render gradients$|waiting on the Prawn project)/
end
end
warnings.should == []
end
end
end
describe 'multiple file rendering' do
it 'renders multiple files on to the same PDF' do
Prawn::Document.generate("#{root}/spec/sample_output/_multiple.pdf") do |prawn|
width = prawn.bounds.width
y = prawn.bounds.top - 12
prawn.draw_text 'This is multiple SVGs being output to the same PDF', at: [0, y]
y -= 12
prawn.svg File.read("#{root}/spec/sample_svg/arcs01.svg"), at: [0, y], width: width / 2, enable_web_requests: false
prawn.svg File.read("#{root}/spec/sample_svg/circle01.svg"), at: [width / 2, y], width: width / 2, enable_web_requests: false
y -= 120
prawn.draw_text 'Here are some more PDFs below', at: [0, y]
y -= 12
prawn.svg File.read("#{root}/spec/sample_svg/quad01.svg"), at: [0, y], width: width / 3, enable_web_requests: false
prawn.svg File.read("#{root}/spec/sample_svg/rect01.svg"), at: [width / 3, y], width: width / 3, enable_web_requests: false
prawn.svg File.read("#{root}/spec/sample_svg/rect02.svg"), at: [width / 3 * 2, y], width: width / 3, enable_web_requests: false
end
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/ 0000775 0000000 0000000 00000000000 15155455534 0017460 5 ustar 00root root 0000000 0000000 mogest-prawn-svg-98d5f7a/spec/prawn/svg/ 0000775 0000000 0000000 00000000000 15155455534 0020257 5 ustar 00root root 0000000 0000000 mogest-prawn-svg-98d5f7a/spec/prawn/svg/attributes/ 0000775 0000000 0000000 00000000000 15155455534 0022445 5 ustar 00root root 0000000 0000000 mogest-prawn-svg-98d5f7a/spec/prawn/svg/attributes/clip_path_spec.rb 0000664 0000000 0000000 00000017617 15155455534 0025763 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::Attributes::ClipPath do
let(:font_registry) { Prawn::SVG::FontRegistry.new('Helvetica' => { normal: nil }) }
let(:document) { Prawn::SVG::Document.new(svg, [800, 600], { enable_web_requests: false }, font_registry: font_registry) }
let(:element) { Prawn::SVG::Elements::Root.new(document, document.root, []) }
let(:flattened_calls) { flatten_calls(element.base_calls) }
before { element.process }
describe 'clip-rule' do
context 'with default clip-rule (nonzero) and single child' do
let(:svg) do
<<~SVG
SVG
end
it 'generates a clip call without clip_rule' do
clip_call = flattened_calls.detect { |c| c[0] == 'clip' }
expect(clip_call).to eq ['clip', [], {}]
end
end
context 'with clip-rule="evenodd" on single-child clipPath' do
let(:svg) do
<<~SVG
SVG
end
it 'generates a clip call with even_odd clip_rule' do
clip_call = flattened_calls.detect { |c| c[0] == 'clip' }
expect(clip_call).to eq ['clip', [], { clip_rule: :even_odd }]
end
end
context 'with clip-rule="evenodd" on multi-child clipPath' do
let(:svg) do
<<~SVG
SVG
end
it 'uses nonzero to preserve correct union semantics' do
clip_call = flattened_calls.detect { |c| c[0] == 'clip' }
expect(clip_call).to eq ['clip', [], {}]
end
end
context 'with clip-rule inherited from parent group on single-child clipPath' do
let(:svg) do
<<~SVG
SVG
end
it 'inherits evenodd from the parent group' do
clip_call = flattened_calls.detect { |c| c[0] == 'clip' }
expect(clip_call).to eq ['clip', [], { clip_rule: :even_odd }]
end
end
end
describe 'clipPathUnits' do
context 'with clipPathUnits="userSpaceOnUse" (default)' do
let(:svg) do
<<~SVG
SVG
end
it 'uses the clip path coordinates as-is' do
rect_call = flattened_calls.detect { |c| c[0] == 'rectangle' }
expect(rect_call).not_to be_nil
end
end
context 'with clipPathUnits="objectBoundingBox"' do
let(:svg) do
<<~SVG
SVG
end
it 'scales clip path coordinates to the element bounding box' do
# The clipped rect is at x=50, y=50, w=100, h=80
# In Prawn coords: left=50, top=150, right=150, bottom=70
# The clip rect is x=0..0.5, y=0..1 in objectBoundingBox
# So it should map to: x=50..100, y=70..150 (left half of the element)
rect_calls = flattened_calls.select { |c| c[0] == 'rectangle' }
clip_rect = rect_calls.first
expect(clip_rect).not_to be_nil
_, width, height = clip_rect[1]
expect(width).to be_within(0.01).of(50.0) # 0.5 * 100
expect(height).to be_within(0.01).of(80.0) # 1.0 * 80
end
end
context 'with clipPathUnits="objectBoundingBox" on element without bounding box' do
let(:svg) do
<<~SVG
SVG
end
it 'skips the clip path and adds a warning' do
expect(document.warnings).to include(
'clipPath with clipPathUnits="objectBoundingBox" requires element to have a bounding box'
)
end
end
context 'with clipPathUnits="objectBoundingBox" using a circle' do
let(:svg) do
<<~SVG
SVG
end
it 'scales the circle to the element bounding box' do
clip_call = flattened_calls.detect { |c| c[0] == 'clip' }
expect(clip_call).not_to be_nil
end
end
end
describe 'text in clip paths' do
context 'with text inside a clipPath' do
let(:svg) do
<<~SVG
Hello
SVG
end
it 'uses a soft_mask instead of a clip call' do
clip_call = flattened_calls.detect { |c| c[0] == 'clip' }
expect(clip_call).to be_nil
mask_call = flattened_calls.detect { |c| c[0] == 'soft_mask' }
expect(mask_call).not_to be_nil
end
it 'generates an svg:render call for the text element' do
render_call = flattened_calls.detect { |c| c[0] == 'svg:render' }
expect(render_call).not_to be_nil
end
it 'does not produce any warnings' do
expect(document.warnings).to be_empty
end
end
context 'with text and shapes mixed in a clipPath' do
let(:svg) do
<<~SVG
Hello
SVG
end
it 'uses soft_mask when text is present' do
clip_call = flattened_calls.detect { |c| c[0] == 'clip' }
expect(clip_call).to be_nil
mask_call = flattened_calls.detect { |c| c[0] == 'soft_mask' }
expect(mask_call).not_to be_nil
end
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/attributes/opacity_spec.rb 0000664 0000000 0000000 00000005140 15155455534 0025454 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Attributes::Opacity do
class OpacityTestElement
include Prawn::SVG::Attributes::Opacity
attr_accessor :properties, :state
def initialize
@properties = ::Prawn::SVG::Properties.new
@state = ::Prawn::SVG::State.new
end
def computed_properties
@state.computed_properties
end
end
let(:element) { OpacityTestElement.new }
describe '#parse_opacity_attributes_and_call' do
subject { element.parse_opacity_attributes_and_call }
context 'with no opacity specified' do
it 'does nothing' do
expect(element).not_to receive(:add_call_and_enter)
subject
end
end
context 'with opacity' do
it 'sets fill and stroke opacity' do
element.computed_properties.opacity = '0.4'
expect(element).to receive(:add_call_and_enter).with('transparent', 0.4, 0.4)
subject
expect(element.state.opacity).to eq 0.4
expect(element.state.last_fill_opacity).to eq 0.4
expect(element.state.last_stroke_opacity).to eq 0.4
end
end
context 'with just fill opacity' do
it 'sets fill opacity and sets stroke opacity to 1' do
element.computed_properties.fill_opacity = '0.4'
expect(element).to receive(:add_call_and_enter).with('transparent', 0.4, 1)
subject
expect(element.state.opacity).to eq 1
expect(element.state.last_fill_opacity).to eq 0.4
expect(element.state.last_stroke_opacity).to eq 1
end
end
context 'with an existing stroke opacity' do
it 'multiplies the new opacity by the old' do
element.state.opacity = 0.5
element.computed_properties.fill_opacity = '0.4'
element.computed_properties.stroke_opacity = '0.5'
expect(element).to receive(:add_call_and_enter).with('transparent', 0.2, 0.25)
subject
expect(element.state.opacity).to eq 0.5
expect(element.state.last_fill_opacity).to eq 0.2
expect(element.state.last_stroke_opacity).to eq 0.25
end
end
context 'with stroke, fill, and opacity all specified' do
it 'choses the lower of them' do
element.computed_properties.fill_opacity = '0.4'
element.computed_properties.stroke_opacity = '0.6'
element.computed_properties.opacity = '0.5'
expect(element).to receive(:add_call_and_enter).with('transparent', 0.2, 0.3)
subject
expect(element.state.opacity).to eq 0.5
expect(element.state.last_fill_opacity).to eq 0.2
expect(element.state.last_stroke_opacity).to eq 0.3
end
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/attributes/stroke_spec.rb 0000664 0000000 0000000 00000011611 15155455534 0025313 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::Attributes::Stroke do
let(:document) { Prawn::SVG::Document.new(svg, [800, 600], { width: 800, height: 600, enable_web_requests: false }) }
subject do
Prawn::SVG::Elements::Line.new(document, document.root, [], fake_state)
end
describe 'stroke-dashoffset' do
context 'with a positive dashoffset' do
let(:svg) { ' ' }
it 'passes phase option to dash call' do
subject.process
dash_call = subject.base_calls.detect { |c| c[0] == 'dash' }
expect(dash_call).to eq ['dash', [[10.0, 5.0]], { phase: 3.0 }, []]
end
end
context 'with a negative dashoffset' do
let(:svg) { ' ' }
it 'passes negative phase option to dash call' do
subject.process
dash_call = subject.base_calls.detect { |c| c[0] == 'dash' }
expect(dash_call).to eq ['dash', [[10.0, 5.0]], { phase: -7.0 }, []]
end
end
context 'with zero dashoffset' do
let(:svg) { ' ' }
it 'does not pass phase option' do
subject.process
dash_call = subject.base_calls.detect { |c| c[0] == 'dash' }
expect(dash_call).to eq ['dash', [[10.0, 5.0]], {}, []]
end
end
context 'with no dashoffset specified' do
let(:svg) { ' ' }
it 'does not pass phase option' do
subject.process
dash_call = subject.base_calls.detect { |c| c[0] == 'dash' }
expect(dash_call).to eq ['dash', [[10.0, 5.0]], {}, []]
end
end
context 'with dashoffset set via style attribute' do
let(:svg) { ' ' }
it 'passes phase option to dash call' do
subject.process
dash_call = subject.base_calls.detect { |c| c[0] == 'dash' }
expect(dash_call).to eq ['dash', [[10.0, 5.0]], { phase: 8.0 }, []]
end
end
context 'with inherited dashoffset' do
let(:svg) { ' ' }
let(:element) { Prawn::SVG::Elements::Container.new(document, document.root, [], fake_state) }
it 'inherits the dashoffset from the parent' do
element.process
dash_call = flatten_calls(element.base_calls).detect { |c| c[0] == 'dash' }
expect(dash_call).to eq ['dash', [[10.0, 5.0]], { phase: 5.0 }]
end
end
context 'with inherited dashoffset overridden to zero on child' do
let(:svg) { ' ' }
let(:element) { Prawn::SVG::Elements::Container.new(document, document.root, [], fake_state) }
it 'uses the child zero offset, not the parent offset' do
element.process
dash_call = flatten_calls(element.base_calls).detect { |c| c[0] == 'dash' }
expect(dash_call).to eq ['dash', [[10.0, 5.0]], {}]
end
end
end
describe 'stroke-miterlimit' do
context 'with a miterlimit attribute' do
let(:svg) { ' ' }
it 'generates a miter_limit call' do
subject.process
call = subject.base_calls.detect { |c| c[0] == 'miter_limit' }
expect(call).to eq ['miter_limit', [8.0], {}, []]
end
end
context 'with miterlimit set via style attribute' do
let(:svg) { ' ' }
it 'generates a miter_limit call' do
subject.process
call = subject.base_calls.detect { |c| c[0] == 'miter_limit' }
expect(call).to eq ['miter_limit', [10.0], {}, []]
end
end
context 'with miterlimit less than 1' do
let(:svg) { ' ' }
it 'rejects the invalid value' do
subject.process
call = subject.base_calls.detect { |c| c[0] == 'miter_limit' }
expect(call).to be_nil
end
end
context 'with inherited miterlimit' do
let(:svg) { ' ' }
let(:element) { Prawn::SVG::Elements::Container.new(document, document.root, [], fake_state) }
it 'inherits the miterlimit from the parent' do
element.process
call = flatten_calls(element.base_calls).detect { |c| c[0] == 'miter_limit' }
expect(call).to eq ['miter_limit', [12.0], {}]
end
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/attributes/transform_spec.rb 0000664 0000000 0000000 00000003132 15155455534 0026016 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Attributes::Transform do
class TransformTestElement
include Prawn::SVG::Attributes::Transform
attr_accessor :attributes, :warnings
def initialize
@warnings = []
@attributes = {}
end
def transformable?
true
end
end
let(:element) { TransformTestElement.new }
subject { element.send :parse_transform_attribute_and_call }
context 'when a non-identity matrix is requested' do
let(:transform) { 'translate(-5.5)' }
it 'passes the transform and executes the returned matrix' do
expect(element).to receive(:parse_transform_attribute).with(transform)
expect(element).to receive(:matrix_for_pdf).and_return([1, 2, 3, 4, 5, 6])
expect(element).to receive(:add_call_and_enter).with('transformation_matrix', 1, 2, 3, 4, 5, 6)
element.attributes['transform'] = transform
subject
end
end
context 'when an identity matrix is requested' do
let(:transform) { 'translate(0)' }
it 'does not execute any commands' do
expect(element).to receive(:parse_transform_attribute).with(transform)
expect(element).to receive(:matrix_for_pdf).and_return([1, 0, 0, 1, 0, 0])
expect(element).not_to receive(:add_call_and_enter)
element.attributes['transform'] = transform
subject
end
end
context 'when transform is blank' do
it 'does nothing' do
expect(element).not_to receive(:parse_transform_attribute)
expect(element).not_to receive(:matrix_for_pdf)
expect(element).not_to receive(:add_call_and_enter)
subject
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/calculators/ 0000775 0000000 0000000 00000000000 15155455534 0022573 5 ustar 00root root 0000000 0000000 mogest-prawn-svg-98d5f7a/spec/prawn/svg/calculators/aspect_ratio_spec.rb 0000664 0000000 0000000 00000006727 15155455534 0026623 0 ustar 00root root 0000000 0000000 require "#{File.dirname(__FILE__)}/../../../spec_helper"
describe Prawn::SVG::Calculators::AspectRatio do
def test(*args)
aspect = Prawn::SVG::Calculators::AspectRatio.new(*args)
[[aspect.width, aspect.height], [aspect.x, aspect.y]]
end
it 'handles none' do
expect(test('none', [50, 80], [100, 100])).to eq [[50, 80], [0, 0]]
expect(test('none', [100, 100], [50, 80])).to eq [[100, 100], [0, 0]]
end
context 'using meet' do
context 'with smaller containers than objects' do
let(:coords) { [[50, 80], [100, 100]] }
it 'correctly calculates the result' do
expect(test('xMidYMid meet', *coords)).to eq [[50, 50], [0, 15]]
expect(test('xMinYMin meet', *coords)).to eq [[50, 50], [0, 0]]
expect(test('xMaxYMax meet', *coords)).to eq [[50, 50], [0, 30]]
end
end
context 'with bigger containers than objects' do
let(:coords) { [[100, 80], [50, 50]] }
it 'correctly calculates the result' do
expect(test('xMidYMid meet', *coords)).to eq [[80, 80], [10, 0]]
expect(test('xMinYMin meet', *coords)).to eq [[80, 80], [0, 0]]
expect(test('xMaxYMax meet', *coords)).to eq [[80, 80], [20, 0]]
end
end
context 'with bigger square containers' do
let(:coords) { [[100, 100], [50, 80]] }
it 'correctly calculates the result' do
expect(test('xMidYMid meet', *coords)).to eq [[62.5, 100], [18.75, 0]]
expect(test('xMinYMin meet', *coords)).to eq [[62.5, 100], [0, 0]]
expect(test('xMaxYMax meet', *coords)).to eq [[62.5, 100], [37.5, 0]]
end
end
context 'with oddly shaped containers' do
let(:coords) { [[100, 20], [50, 50]] }
it 'correctly calculates the result' do
expect(test('xMidYMid meet', *coords)).to eq [[20, 20], [40, 0]]
expect(test('xMinYMin meet', *coords)).to eq [[20, 20], [0, 0]]
expect(test('xMaxYMax meet', *coords)).to eq [[20, 20], [80, 0]]
end
end
end
context 'using slice' do
context 'with smaller containers than objects' do
let(:coords) { [[50, 80], [100, 100]] }
it 'correctly calculates the result' do
expect(test('xMidYMid slice', *coords)).to eq [[80, 80], [-15, 0]]
expect(test('xMinYMin slice', *coords)).to eq [[80, 80], [0, 0]]
expect(test('xMaxYMax slice', *coords)).to eq [[80, 80], [-30, 0]]
end
end
context 'with bigger containers than objects' do
let(:coords) { [[100, 80], [50, 50]] }
it 'correctly calculates the result' do
expect(test('xMidYMid slice', *coords)).to eq [[100, 100], [0, -10]]
expect(test('xMinYMin slice', *coords)).to eq [[100, 100], [0, 0]]
expect(test('xMaxYMax slice', *coords)).to eq [[100, 100], [0, -20]]
end
end
context 'with oddly shaped containers' do
let(:coords) { [[100, 20], [50, 50]] }
it 'correctly calculates the result' do
expect(test('xMidYMid slice', *coords)).to eq [[100, 100], [0, -40]]
expect(test('xMinYMin slice', *coords)).to eq [[100, 100], [0, 0]]
expect(test('xMaxYMax slice', *coords)).to eq [[100, 100], [0, -80]]
end
end
end
it "defaults to 'xMidYMid meet' if nothing is supplied" do
expect(test('', [50, 80], [100, 100])).to eq test 'xMidYMid meet', [50, 80], [100, 100]
end
it "defaults to 'xMidYMid meet' if something invalid is supplied" do
expect(test('completely invalid', [50, 80], [100, 100])).to eq test 'xMidYMid meet', [50, 80], [100, 100]
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/calculators/document_sizing_spec.rb 0000664 0000000 0000000 00000012546 15155455534 0027343 0 ustar 00root root 0000000 0000000 require "#{File.dirname(__FILE__)}/../../../spec_helper"
describe Prawn::SVG::Calculators::DocumentSizing do
let(:attributes) do
{ 'width' => '150', 'height' => '200', 'viewBox' => '0 -30 300 800', 'preserveAspectRatio' => 'xMaxYMid meet' }
end
let(:bounds) { [1200, 800] }
let(:sizing) { Prawn::SVG::Calculators::DocumentSizing.new(bounds, attributes) }
describe '#initialize' do
it 'takes bounds and a set of attributes and calls set_from_attributes' do
expect(sizing.instance_variable_get(:@bounds)).to eq bounds
expect(sizing.instance_variable_get(:@document_width)).to eq '150'
end
end
describe '#set_from_attributes' do
let(:sizing) { Prawn::SVG::Calculators::DocumentSizing.new(bounds) }
it 'sets ivars from the passed-in attributes hash' do
sizing.set_from_attributes(attributes)
expect(sizing.instance_variable_get(:@document_width)).to eq '150'
expect(sizing.instance_variable_get(:@document_height)).to eq '200'
expect(sizing.instance_variable_get(:@view_box)).to eq '0 -30 300 800'
expect(sizing.instance_variable_get(:@preserve_aspect_ratio)).to eq 'xMaxYMid meet'
end
end
describe '#calculate' do
it 'calculates the document sizing measurements for a given set of inputs' do
sizing.calculate
expect(sizing.x_offset).to eq(-75 / 0.25)
expect(sizing.y_offset).to eq(-30)
expect(sizing.x_scale).to eq 0.25
expect(sizing.y_scale).to eq 0.25
expect(sizing.viewport_width).to eq 300
expect(sizing.viewport_height).to eq 800
expect(sizing.output_width).to eq 150
expect(sizing.output_height).to eq 200
end
it 'scales again based on requested width' do
sizing.requested_width = 75
sizing.calculate
expect(sizing.x_scale).to eq 0.125
expect(sizing.y_scale).to eq 0.125
expect(sizing.viewport_width).to eq 300
expect(sizing.viewport_height).to eq 800
expect(sizing.output_width).to eq 75
expect(sizing.output_height).to eq 100
end
it 'scales again based on requested height' do
sizing.requested_height = 100
sizing.calculate
expect(sizing.x_scale).to eq 0.125
expect(sizing.y_scale).to eq 0.125
expect(sizing.viewport_width).to eq 300
expect(sizing.viewport_height).to eq 800
expect(sizing.output_width).to eq 75
expect(sizing.output_height).to eq 100
end
it 'correctly handles % values being passed in' do
sizing.document_width = sizing.document_height = '50%'
sizing.calculate
expect(sizing.output_width).to eq 600
expect(sizing.output_height).to eq 400
end
context 'when SVG does not specify width and height' do
context 'when a viewBox is specified' do
let(:attributes) { { 'viewBox' => '0 0 100 200' } }
it 'defaults to 100% width and uses the viewbox ratio for height' do
sizing.calculate
expect(sizing.viewport_width).to eq 100
expect(sizing.viewport_height).to eq 200
expect(sizing.output_width).to eq 1200
expect(sizing.output_height).to eq 2400
end
end
context 'when a requested width and height are supplied' do
let(:attributes) { {} }
it 'uses the requested width and height' do
sizing.requested_width = 550
sizing.requested_height = 400
sizing.calculate
expect(sizing.viewport_width).to eq 550
expect(sizing.viewport_height).to eq 400
expect(sizing.output_width).to eq 550
expect(sizing.output_height).to eq 400
end
end
context 'when a viewBox and a requested width/height are supplied' do
let(:attributes) { { 'viewBox' => '0 0 100 200' } }
it 'uses the requested width and height' do
sizing.requested_width = 550
sizing.requested_height = 400
sizing.calculate
expect(sizing.viewport_width).to eq 100
expect(sizing.viewport_height).to eq 200
expect(sizing.output_width).to eq 550
expect(sizing.output_height).to eq 400
end
end
context 'when a viewBox and a requested width is supplied' do
let(:attributes) { { 'viewBox' => '0 0 100 200' } }
it 'uses the requested width and calculates the height based on the viewBox' do
sizing.requested_width = 550
sizing.calculate
expect(sizing.viewport_width).to eq 100
expect(sizing.viewport_height).to eq 200
expect(sizing.output_width).to eq 550
expect(sizing.output_height).to eq 1100
end
end
context 'when a viewBox and a requested height is supplied' do
let(:attributes) { { 'viewBox' => '0 0 100 200' } }
it 'uses the requested height and calculates the width based on the viewBox' do
sizing.requested_height = 400
sizing.calculate
expect(sizing.viewport_width).to eq 100
expect(sizing.viewport_height).to eq 200
expect(sizing.output_width).to eq 200
expect(sizing.output_height).to eq 400
end
end
context 'when neither viewBox nor requested width/height specified' do
let(:attributes) { {} }
it 'defaults to 100%' do
sizing.calculate
expect(sizing.output_width).to eq 1200
expect(sizing.output_height).to eq 800
end
end
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/calculators/path_length_spec.rb 0000664 0000000 0000000 00000007260 15155455534 0026434 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Calculators::PathLength do
let(:move) { Prawn::SVG::Pathable::Move }
let(:line) { Prawn::SVG::Pathable::Line }
let(:curve) { Prawn::SVG::Pathable::Curve }
let(:close) { Prawn::SVG::Pathable::Close }
describe 'horizontal line' do
subject { described_class.new([move.new([0, 0]), line.new([100, 0])]) }
it 'calculates total length' do
expect(subject.total_length).to eq 100.0
end
it 'returns midpoint correctly' do
x, y, angle = subject.point_at(50)
expect(x).to eq 50.0
expect(y).to eq 0.0
expect(angle).to eq 0.0
end
it 'returns start point' do
x, y, angle = subject.point_at(0)
expect(x).to eq 0.0
expect(y).to eq 0.0
expect(angle).to eq 0.0
end
it 'returns end point' do
x, y, angle = subject.point_at(100)
expect(x).to eq 100.0
expect(y).to eq 0.0
expect(angle).to eq 0.0
end
end
describe 'vertical line' do
subject { described_class.new([move.new([0, 0]), line.new([0, 100])]) }
it 'calculates total length' do
expect(subject.total_length).to eq 100.0
end
it 'returns correct 90 degree angle' do
_, _, angle = subject.point_at(50)
expect(angle).to eq 90.0
end
end
describe 'multiple segments' do
subject do
described_class.new([
move.new([0, 0]),
line.new([100, 0]),
line.new([100, 100])
])
end
it 'calculates cumulative total length' do
expect(subject.total_length).to eq 200.0
end
it 'interpolates across segments' do
x, y, angle = subject.point_at(150)
expect(x).to eq 100.0
expect(y).to eq 50.0
expect(angle).to eq 90.0
end
end
describe 'move command' do
subject do
described_class.new([
move.new([0, 0]),
line.new([50, 0]),
move.new([200, 200]),
line.new([250, 200])
])
end
it 'does not add length for the move' do
expect(subject.total_length).to eq 100.0
end
end
describe 'close command' do
subject do
described_class.new([
move.new([0, 0]),
line.new([100, 0]),
line.new([100, 100]),
close.new([0, 0])
])
end
it 'adds a line back to the subpath start' do
expect(subject.total_length).to be_within(0.01).of(200 + Math.sqrt(20_000))
end
end
describe 'cubic bezier' do
subject do
described_class.new([
move.new([0, 0]),
curve.new([100, 0], [33, 50], [66, 50])
])
end
it 'calculates a reasonable total length' do
expect(subject.total_length).to be > 100
expect(subject.total_length).to be < 200
end
it 'returns a midpoint approximately in the right area' do
x, y, _angle = subject.point_at(subject.total_length / 2.0)
expect(x).to be_within(10).of(50)
expect(y).to be > 0
end
end
describe 'edge cases' do
subject { described_class.new([move.new([0, 0]), line.new([100, 0])]) }
it 'returns nil for past end' do
expect(subject.point_at(101)).to be_nil
end
it 'returns nil for negative distance' do
expect(subject.point_at(-1)).to be_nil
end
end
describe 'empty path' do
subject { described_class.new([move.new([0, 0])]) }
it 'has zero total length' do
expect(subject.total_length).to eq 0.0
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/calculators/pixels_spec.rb 0000664 0000000 0000000 00000004674 15155455534 0025451 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Calculators::Pixels do
class TestPixelsCalculator
include Prawn::SVG::Calculators::Pixels
def computed_properties
Struct.new(:numeric_font_size).new(16)
end
[:x, :y, :pixels, :x_pixels, :y_pixels].each { |method| public method }
end
let(:viewport_sizing) do
instance_double(Prawn::SVG::Calculators::DocumentSizing, viewport_width: 600, viewport_height: 400,
viewport_diagonal: 500, :requested_width= => nil, :requested_height= => nil)
end
let(:document_sizing) do
instance_double(Prawn::SVG::Calculators::DocumentSizing, output_height: 800)
end
let(:state) { instance_double(Prawn::SVG::State, viewport_sizing: viewport_sizing) }
let(:document) { instance_double(Prawn::SVG::Document, sizing: document_sizing) }
subject { TestPixelsCalculator.new }
before do
allow(subject).to receive(:state).and_return(state)
allow(subject).to receive(:document).and_return(document)
end
describe '#pixels' do
it 'converts a variety of measurement units to points' do
expect(subject.pixels(32)).to eq 32.0
expect(subject.pixels(32.0)).to eq 32.0
expect(subject.pixels('32')).to eq 32.0
expect(subject.pixels('32px')).to eq 32.0
expect(subject.pixels('32pt')).to eq 32.0
expect(subject.pixels('32in')).to eq 32.0 * 72
expect(subject.pixels('32pc')).to eq 32.0 * 15
expect(subject.pixels('4em')).to eq 4 * 16
expect(subject.pixels('4ex')).to eq 4 * 8
expect(subject.pixels('32mm')).to be_within(0.0001).of(32 * 72 * 0.0393700787)
expect(subject.pixels('32cm')).to be_within(0.0001).of(32 * 72 * 0.393700787)
expect(subject.pixels('50%')).to eq 250
end
end
describe '#x_pixels' do
it 'uses the viewport width for percentages' do
expect(subject.x_pixels('50')).to eq 50
expect(subject.x_pixels('50%')).to eq 300
end
end
describe '#y_pixels' do
it 'uses the viewport height for percentages' do
expect(subject.y_pixels('50')).to eq 50
expect(subject.y_pixels('50%')).to eq 200
end
end
describe '#x' do
it 'performs the same as #x_pixels' do
expect(subject.x('50')).to eq 50
expect(subject.x('50%')).to eq 300
end
end
describe '#y' do
it 'performs the same as #y_pixels but subtracts the pixels from the page height' do
expect(subject.y('50')).to eq 800 - 50
expect(subject.y('50%')).to eq 800 - 200
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/color_spec.rb 0000664 0000000 0000000 00000002717 15155455534 0022743 0 ustar 00root root 0000000 0000000 require "#{File.dirname(__FILE__)}/../../spec_helper"
describe Prawn::SVG::Color do
describe '::parse' do
def parse(value)
values = Prawn::SVG::CSS::ValuesParser.parse(value)
Prawn::SVG::Color.parse(values[0])
end
it 'converts #xxx to an RGB color' do
expect(parse('#9ab')).to eq Prawn::SVG::Color::RGB.new('99aabb')
end
it 'converts #xxxxxx to an RGB color' do
expect(parse('#9ab123')).to eq Prawn::SVG::Color::RGB.new('9ab123')
end
it 'converts an html colour name to an RGB color' do
expect(parse('White')).to eq Prawn::SVG::Color::RGB.new('ffffff')
end
it 'converts an rgb function to an RGB color' do
expect(parse('rgb(16, 32, 48)')).to eq Prawn::SVG::Color::RGB.new('102030')
expect(parse('rgb(-5, 50%, 120%)')).to eq Prawn::SVG::Color::RGB.new('007fff')
end
it 'converts a CMYK string to an array of numbers' do
expect(parse('device-cmyk(0, 0.32, 0.48, 1.2)')).to eq Prawn::SVG::Color::CMYK.new([0, 32, 48, 100])
expect(parse('device-cmyk(0, 50%, 120%, -5%)')).to eq Prawn::SVG::Color::CMYK.new([0, 50, 100, 0])
end
it "returns nil if the color doesn't exist" do
expect(parse('blurble')).to be nil
end
it 'returns nil if the function has the wrong number of arguments' do
expect(parse('rgb(-1, 0)')).to be nil
end
it "returns nil if it doesn't recognise the function" do
expect(parse('hsl(0, 0, 0)')).to be nil
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/css/ 0000775 0000000 0000000 00000000000 15155455534 0021047 5 ustar 00root root 0000000 0000000 mogest-prawn-svg-98d5f7a/spec/prawn/svg/css/font_face_parser_spec.rb 0000664 0000000 0000000 00000005551 15155455534 0025714 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::CSS::FontFaceParser do
describe '.parse_src' do
it 'parses a simple url() source' do
result = described_class.parse_src('url("font.ttf")')
expect(result).to eq([{ type: :url, url: 'font.ttf', format: nil }])
end
it 'parses a url() with format()' do
result = described_class.parse_src('url("font.ttf") format("truetype")')
expect(result).to eq([{ type: :url, url: 'font.ttf', format: 'truetype' }])
end
it 'parses a local() source' do
result = described_class.parse_src('local("Helvetica")')
expect(result).to eq([{ type: :local, name: 'Helvetica' }])
end
it 'parses multiple comma-separated sources' do
result = described_class.parse_src('local("Helvetica"), url("font.ttf") format("truetype"), url("fallback.otf")')
expect(result).to eq([
{ type: :local, name: 'Helvetica' },
{ type: :url, url: 'font.ttf', format: 'truetype' },
{ type: :url, url: 'fallback.otf', format: nil }
])
end
it 'handles single-quoted strings' do
result = described_class.parse_src("url('font.ttf') format('truetype')")
expect(result).to eq([{ type: :url, url: 'font.ttf', format: 'truetype' }])
end
it 'handles unquoted url values' do
result = described_class.parse_src('url(font.ttf)')
expect(result).to eq([{ type: :url, url: 'font.ttf', format: nil }])
end
it 'handles URLs with commas inside quotes' do
result = described_class.parse_src('url("font,name.ttf"), url("other.ttf")')
expect(result).to eq([
{ type: :url, url: 'font,name.ttf', format: nil },
{ type: :url, url: 'other.ttf', format: nil }
])
end
it 'handles empty src' do
expect(described_class.parse_src('')).to eq([])
end
it 'ignores entries with unknown function types' do
result = described_class.parse_src('something("test"), url("font.ttf")')
expect(result).to eq([{ type: :url, url: 'font.ttf', format: nil }])
end
it 'parses data URLs' do
data_url = 'data:font/ttf;base64,AAEAAAALAI=='
result = described_class.parse_src("url(\"#{data_url}\")")
expect(result).to eq([{ type: :url, url: data_url, format: nil }])
end
it 'parses a complex multi-format chain' do
src = 'local("Font"), url("font.woff2") format("woff2"), url("font.ttf") format("truetype")'
result = described_class.parse_src(src)
expect(result).to eq([
{ type: :local, name: 'Font' },
{ type: :url, url: 'font.woff2', format: 'woff2' },
{ type: :url, url: 'font.ttf', format: 'truetype' }
])
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/css/font_family_parser_spec.rb 0000664 0000000 0000000 00000001625 15155455534 0026275 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::CSS::FontFamilyParser do
describe '#parse' do
it 'correctly handles quotes and escaping' do
tests = {
'' => [],
'font' => ['font'],
'font name, other font' => ['font name', 'other font'],
"'font name', other font" => ['font name', 'other font'],
"'font, name', other font" => ['font, name', 'other font'],
'"font name", other font' => ['font name', 'other font'],
'"font, name", other font' => ['font, name', 'other font'],
'weird \\" name' => ['weird " name'],
'weird\\, name' => ['weird, name'],
' stupid , spacing ' => ['stupid', 'spacing']
}
tests.each do |string, expected|
expect(Prawn::SVG::CSS::FontFamilyParser.parse(string)).to eq expected
end
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/css/selector_parser_spec.rb 0000664 0000000 0000000 00000003754 15155455534 0025613 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::CSS::SelectorParser do
describe '::parse' do
it 'parses a simple selector' do
expect(described_class.parse('div')).to eq [{ name: 'div' }]
expect(described_class.parse('.c1')).to eq [{ class: ['c1'] }]
end
it 'parses a complex selector' do
result = described_class.parse('div#count .c1.c2 > span.large + div~.other:first-child *:nth-child(3)')
expect(result).to eq [
{ name: 'div', id: ['count'] },
{ combinator: :descendant, class: ['c1', 'c2'] },
{ combinator: :child, name: 'span', class: ['large'] },
{ combinator: :adjacent, name: 'div' },
{ combinator: :siblings, class: ['other'], pseudo_class: ['first-child'] },
{ combinator: :descendant, name: '*', pseudo_class: ['nth-child(3)'] }
]
end
it 'parses :lang() pseudo-class' do
expect(described_class.parse(':lang(en)')).to eq [{ pseudo_class: ['lang(en)'] }]
expect(described_class.parse('rect:lang(fr)')).to eq [{ name: 'rect', pseudo_class: ['lang(fr)'] }]
expect(described_class.parse(':lang(en-US)')).to eq [{ pseudo_class: ['lang(en-US)'] }]
end
it 'parses attributes' do
expect(described_class.parse('[abc]')).to eq [{ attribute: [['abc', nil, nil]] }]
expect(described_class.parse('[abc=123]')).to eq [{ attribute: [['abc', '=', '123']] }]
expect(described_class.parse('[abc^=123]')).to eq [{ attribute: [['abc', '^=', '123']] }]
expect(described_class.parse('[ abc ^= 123 ]')).to eq [{ attribute: [['abc', '^=', '123']] }]
expect(described_class.parse("[abc^='123']")).to eq [{ attribute: [['abc', '^=', '123']] }]
expect(described_class.parse("[abc^= '123' ]")).to eq [{ attribute: [['abc', '^=', '123']] }]
expect(described_class.parse("[abc^= '123\\'456' ]")).to eq [{ attribute: [['abc', '^=', '123\'456']] }]
expect(described_class.parse('[abc^= "123\\"456" ]')).to eq [{ attribute: [['abc', '^=', '123"456']] }]
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/css/stylesheets_spec.rb 0000664 0000000 0000000 00000036645 15155455534 0025000 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::CSS::Stylesheets do
describe 'typical usage' do
let(:svg) { <<-SVG }
SVG
it 'associates styles with elements' do
result = Prawn::SVG::CSS::Stylesheets.new(CssParser::Parser.new, REXML::Document.new(svg)).load
width_and_styles = result.map { |k, v| [k.attributes['width'].to_i, v] }.sort_by(&:first)
expected = [
[1, [['fill', '#ff0000', false]]],
[2,
[['fill', '#ff0000', false], ['fill', '#330000', false], ['fill', '#440000', false],
['fill', '#220000', false]]],
[3, [['fill', '#ff0000', false], ['fill', '#00ff00', false]]],
[4,
[['fill', '#ff0000', false], ['fill', '#330000', false], ['fill', '#440000', false],
['fill', '#00ff00', false]]]
]
expected << [5,
[['fill', '#ff0000', false], ['fill', '#330000', false], ['fill', '#440000', false],
['fill', '#00ff00', false]]]
expected.push(
[6, [['fill', '#ff0000', false], ['fill', '#441234', false], ['fill', '#0000ff', false]]],
[7, [['fill', '#550000', false]]],
[8, [['fill', '#660000', false]]],
[9, [['fill', '#770000', false]]],
[10, [['fill', '#880000', false]]],
[11, [['fill', '#990000', false]]],
[12, [['fill', '#aa0000', false]]],
[13, [['fill', '#bb0000', false]]],
[14, [['fill', '#cc0000', false]]],
[15, [['fill', '#dd0000', false]]],
[16, [['fill', '#ee0000', false]]]
)
expect(width_and_styles).to eq(expected)
end
end
describe '@font-face extraction' do
let(:svg) do
<<~SVG
SVG
end
it 'extracts @font-face rules separately from element styles' do
stylesheets = Prawn::SVG::CSS::Stylesheets.new(CssParser::Parser.new, REXML::Document.new(svg))
element_styles = stylesheets.load
expect(stylesheets.font_face_rules.length).to eq(1)
rule = stylesheets.font_face_rules.first
decl_hash = {}
rule.each { |name, value, _| decl_hash[name] = value }
expect(decl_hash['font-family']).to eq('"CustomFont"')
expect(decl_hash['src']).to eq('url("custom.ttf") format("truetype")')
expect(decl_hash['font-weight']).to eq('bold')
width_and_styles = element_styles.map { |k, v| [k.attributes['width'].to_i, v] }
expect(width_and_styles).to eq([[1, [['fill', 'red', false]]]])
end
it 'handles multiple @font-face rules' do
svg_with_two = <<~SVG
SVG
stylesheets = Prawn::SVG::CSS::Stylesheets.new(CssParser::Parser.new, REXML::Document.new(svg_with_two))
stylesheets.load
expect(stylesheets.font_face_rules.length).to eq(2)
end
end
describe '@import' do
let(:url_loader) { instance_double(Prawn::SVG::UrlLoader) }
let(:warnings) { [] }
it 'loads @import url() rules via the url_loader' do
imported_css = 'rect { stroke: blue; }'
allow(url_loader).to receive(:load).with('external.css').and_return(imported_css)
svg = <<~SVG
SVG
stylesheets = Prawn::SVG::CSS::Stylesheets.new(
CssParser::Parser.new, REXML::Document.new(svg),
url_loader: url_loader, warnings: warnings
)
result = stylesheets.load
styles = result.values.first
expect(styles).to include(['stroke', 'blue', false])
expect(styles).to include(['fill', 'red', false])
end
it 'loads @import with bare string syntax' do
imported_css = 'circle { fill: green; }'
allow(url_loader).to receive(:load).with('styles.css').and_return(imported_css)
svg = <<~SVG
SVG
stylesheets = Prawn::SVG::CSS::Stylesheets.new(
CssParser::Parser.new, REXML::Document.new(svg),
url_loader: url_loader, warnings: warnings
)
result = stylesheets.load
circle_styles = result.detect { |k, _| k.name == 'circle' }&.last
expect(circle_styles).to include(['fill', 'green', false])
end
it 'handles nested @import rules' do
allow(url_loader).to receive(:load).with('base.css').and_return('@import url("nested.css"); rect { stroke: blue; }')
allow(url_loader).to receive(:load).with('nested.css').and_return('rect { stroke-width: 2; }')
svg = <<~SVG
SVG
stylesheets = Prawn::SVG::CSS::Stylesheets.new(
CssParser::Parser.new, REXML::Document.new(svg),
url_loader: url_loader, warnings: warnings
)
result = stylesheets.load
styles = result.values.first
expect(styles).to include(['stroke-width', '2', false])
expect(styles).to include(['stroke', 'blue', false])
end
it 'prevents circular @import references' do
allow(url_loader).to receive(:load).with('a.css').and_return('@import url("b.css"); rect { fill: red; }')
allow(url_loader).to receive(:load).with('b.css').and_return('@import url("a.css"); rect { stroke: blue; }')
svg = <<~SVG
SVG
stylesheets = Prawn::SVG::CSS::Stylesheets.new(
CssParser::Parser.new, REXML::Document.new(svg),
url_loader: url_loader, warnings: warnings
)
result = stylesheets.load
styles = result.values.first
expect(styles).to include(['fill', 'red', false])
expect(styles).to include(['stroke', 'blue', false])
end
it 'warns and continues when @import fails to load' do
allow(url_loader).to receive(:load).with('missing.css').and_raise(Prawn::SVG::UrlLoader::Error, 'not found')
svg = <<~SVG
SVG
stylesheets = Prawn::SVG::CSS::Stylesheets.new(
CssParser::Parser.new, REXML::Document.new(svg),
url_loader: url_loader, warnings: warnings
)
result = stylesheets.load
expect(warnings).to include('Failed to load @import CSS from missing.css: not found')
styles = result.values.first
expect(styles).to include(['fill', 'red', false])
end
it 'does not attempt @import when no url_loader is provided' do
svg = <<~SVG
SVG
stylesheets = Prawn::SVG::CSS::Stylesheets.new(
CssParser::Parser.new, REXML::Document.new(svg)
)
result = stylesheets.load
styles = result.values.first
expect(styles).to include(['fill', 'red', false])
end
end
describe ':lang() pseudo-class' do
it 'matches elements by xml:lang attribute' do
svg = <<~SVG
SVG
css_parser = CssParser::Parser.new
css_parser.add_block!('rect:lang(en) { fill: red; } rect:lang(fr) { fill: blue; }')
result = Prawn::SVG::CSS::Stylesheets.new(css_parser, REXML::Document.new(svg)).load
width_and_styles = result.map { |k, v| [k.attributes['width'].to_i, v] }.sort_by(&:first)
expect(width_and_styles).to eq([
[1, [['fill', 'red', false]]],
[2, [['fill', 'blue', false]]]
])
end
it 'matches with prefix matching (en matches en-US)' do
svg = <<~SVG
SVG
css_parser = CssParser::Parser.new
css_parser.add_block!('rect:lang(en) { fill: red; }')
result = Prawn::SVG::CSS::Stylesheets.new(css_parser, REXML::Document.new(svg)).load
styles = result.values.first
expect(styles).to eq([['fill', 'red', false]])
end
it 'inherits language from ancestors' do
svg = <<~SVG
SVG
css_parser = CssParser::Parser.new
css_parser.add_block!('rect:lang(en) { fill: red; }')
result = Prawn::SVG::CSS::Stylesheets.new(css_parser, REXML::Document.new(svg)).load
styles = result.values.first
expect(styles).to eq([['fill', 'red', false]])
end
end
describe ':link pseudo-class' do
it 'matches elements with href attribute' do
svg = <<~SVG
SVG
css_parser = CssParser::Parser.new
css_parser.add_block!('a:link { fill: red; }')
result = Prawn::SVG::CSS::Stylesheets.new(css_parser, REXML::Document.new(svg)).load
matched = result.map { |k, _| k.name }.sort
expect(matched).to eq(%w[a a])
end
end
describe ':visited, :hover, :active, :focus pseudo-classes' do
%w[visited hover active focus].each do |pc|
it ":#{pc} never matches any elements" do
svg = <<~SVG
SVG
css_parser = CssParser::Parser.new
css_parser.add_block!("a:#{pc} { fill: red; }")
result = Prawn::SVG::CSS::Stylesheets.new(css_parser, REXML::Document.new(svg)).load
expect(result).to be_empty
end
end
end
describe 'unknown pseudo-classes' do
it 'does not match any elements for unknown pseudo-classes' do
svg = <<~SVG
SVG
css_parser = CssParser::Parser.new
css_parser.add_block!('rect:bogus { fill: red; }')
result = Prawn::SVG::CSS::Stylesheets.new(css_parser, REXML::Document.new(svg)).load
expect(result).to be_empty
end
end
describe ':nth-child() formula syntax' do
let(:svg) do
<<~SVG
SVG
end
def matched_widths(css_rule)
css_parser = CssParser::Parser.new
css_parser.add_block!(css_rule)
result = Prawn::SVG::CSS::Stylesheets.new(css_parser, REXML::Document.new(svg)).load
result.map { |k, _| k.attributes['width'].to_i }.sort
end
it 'handles odd keyword' do
expect(matched_widths('rect:nth-child(odd) { fill: red; }')).to eq([1, 3, 5])
end
it 'handles even keyword' do
expect(matched_widths('rect:nth-child(even) { fill: red; }')).to eq([2, 4, 6])
end
it 'handles bare number' do
expect(matched_widths('rect:nth-child(3) { fill: red; }')).to eq([3])
end
it 'handles 2n+1 formula' do
expect(matched_widths('rect:nth-child(2n+1) { fill: red; }')).to eq([1, 3, 5])
end
it 'handles 2n formula (every 2nd)' do
expect(matched_widths('rect:nth-child(2n) { fill: red; }')).to eq([2, 4, 6])
end
it 'handles 3n formula' do
expect(matched_widths('rect:nth-child(3n) { fill: red; }')).to eq([3, 6])
end
it 'handles n+3 formula (from 3rd onward)' do
expect(matched_widths('rect:nth-child(n+3) { fill: red; }')).to eq([3, 4, 5, 6])
end
it 'handles -n+3 formula (first 3)' do
expect(matched_widths('rect:nth-child(-n+3) { fill: red; }')).to eq([1, 2, 3])
end
it 'handles 3n+2 formula' do
expect(matched_widths('rect:nth-child(3n+2) { fill: red; }')).to eq([2, 5])
end
end
describe 'style tag parsing' do
let(:svg) do
<<~SVG
SVG
end
it 'scans the document for style tags and adds the style information to the css parser' do
css_parser = instance_double(CssParser::Parser)
expect(css_parser).to receive(:add_block!).with("a\n before>\n x y\n inside <>>\n k j\n after\nz")
expect(css_parser).to receive(:add_block!).with('hello')
allow(css_parser).to receive(:each_rule_set)
Prawn::SVG::CSS::Stylesheets.new(css_parser, REXML::Document.new(svg)).load
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/css/values_parser_spec.rb 0000664 0000000 0000000 00000000636 15155455534 0025266 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::CSS::ValuesParser do
it 'parses specified values' do
values = 'hello world url("#myid") no-quote(very good) escaping(")\\")ok") rgb( 1,4, 5 )'
expect(described_class.parse(values)).to eq [
'hello',
'world',
['url', ['#myid']],
['no-quote', ['very good']],
['escaping', [')")ok']],
['rgb', %w[1 4 5]]
]
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/document_spec.rb 0000664 0000000 0000000 00000021402 15155455534 0023433 0 ustar 00root root 0000000 0000000 require "#{File.dirname(__FILE__)}/../../spec_helper"
describe Prawn::SVG::Document do
let(:bounds) { [100, 100] }
let(:options) { { enable_web_requests: false } }
describe '#initialize' do
context 'with a well-formed document' do
let(:svg) { ' ' }
let(:options) { { color_mode: :cmyk, enable_web_requests: false } }
it 'parses the XML and yields itself to its block' do
yielded = nil
document = Prawn::SVG::Document.new(svg, bounds, options) do |doc|
yielded = doc
end
expect(yielded).to eq document
expect(document.color_mode).to eq :cmyk
expect(document.root.name).to eq 'svg'
end
end
context 'when unparsable XML is provided' do
let(:svg) { "this isn't SVG data" }
it "raises an exception, passing on REXML's error message" do
expect do
Prawn::SVG::Document.new(svg, bounds, options)
end.to raise_error Prawn::SVG::Document::InvalidSVGData,
/\AThe data supplied is not a valid SVG document.+Malformed.+#{Regexp.escape(svg)}/m
end
end
context 'when broken XML is provided' do
let(:svg) { ' ' }
it "raises an exception, passing on REXML's error message" do
expect do
Prawn::SVG::Document.new(svg, bounds, options)
end.to raise_error Prawn::SVG::Document::InvalidSVGData,
/\AThe data supplied is not a valid SVG document.+Missing end tag for 'g'/m
end
end
context 'with @font-face rules' do
let(:sample_ttf_dir) { File.expand_path('../../sample_ttf', __dir__) }
let(:font_filename) { 'OpenSans-SemiboldItalic.ttf' }
let(:font_path) { File.join(sample_ttf_dir, font_filename) }
let(:file_options) { { enable_file_requests_with_root: sample_ttf_dir, enable_web_requests: false } }
it 'registers @font-face fonts with the font registry' do
svg = <<~SVG
SVG
font_registry = Prawn::SVG::FontRegistry.new({})
Prawn::SVG::Document.new(svg, bounds, file_options, font_registry: font_registry)
expect(font_registry.installed_fonts['TestFont']).to be_a(Hash)
expect(font_registry.installed_fonts['TestFont'][:bold_italic]).to be_a(String)
end
it 'skips unsupported font formats' do
svg = <<~SVG
SVG
font_registry = Prawn::SVG::FontRegistry.new({})
Prawn::SVG::Document.new(svg, bounds, options, font_registry: font_registry)
expect(font_registry.installed_fonts['WoffFont']).to be_nil
end
it 'tries multiple sources and uses the first successful one' do
svg = <<~SVG
SVG
font_registry = Prawn::SVG::FontRegistry.new({})
document = Prawn::SVG::Document.new(svg, bounds, file_options, font_registry: font_registry)
expect(font_registry.installed_fonts['MultiFont']).to be_a(Hash)
expect(font_registry.installed_fonts['MultiFont'][:normal]).to be_a(String)
expect(document.warnings).to include(/Failed to load.*nonexistent\.ttf/)
end
it 'supports local() font references' do
svg = <<~SVG
SVG
font_registry = Prawn::SVG::FontRegistry.new({
'ExistingFont' => { normal: '/path/to/existing.ttf', bold: '/path/to/bold.ttf' }
})
Prawn::SVG::Document.new(svg, bounds, options, font_registry: font_registry)
expect(font_registry.installed_fonts['AliasFont']).to be_a(Hash)
expect(font_registry.installed_fonts['AliasFont'][:bold]).to eq('/path/to/bold.ttf')
end
it 'does not load file URLs when enable_file_requests_with_root is not set' do
svg = <<~SVG
SVG
font_registry = Prawn::SVG::FontRegistry.new({})
document = Prawn::SVG::Document.new(svg, bounds, { enable_web_requests: false }, font_registry: font_registry)
expect(font_registry.installed_fonts['FileFont']).to be_nil
expect(document.warnings).to include(/Failed to load/)
end
it 'does not load web URLs when enable_web_requests is false' do
svg = <<~SVG
SVG
font_registry = Prawn::SVG::FontRegistry.new({})
document = Prawn::SVG::Document.new(svg, bounds, { enable_web_requests: false }, font_registry: font_registry)
expect(font_registry.installed_fonts['WebFont']).to be_nil
expect(document.warnings).to include(/Failed to load/)
end
it 'skips @font-face rules without font-family' do
svg = <<~SVG
SVG
font_registry = Prawn::SVG::FontRegistry.new({})
Prawn::SVG::Document.new(svg, bounds, options, font_registry: font_registry)
expect(font_registry.installed_fonts).not_to have_key(nil)
end
end
context 'with @import rules' do
let(:sample_css_dir) { File.expand_path('../../sample_css', __dir__) }
def svg_with_import(url)
<<~SVG
SVG
end
it 'loads @import CSS from files when enable_file_requests_with_root is set' do
css_path = File.join(sample_css_dir, 'import_nested.css')
document = Prawn::SVG::Document.new(
svg_with_import(css_path), bounds,
{ enable_file_requests_with_root: sample_css_dir, enable_web_requests: false }
)
expect(document.warnings).to be_empty
end
it 'does not load @import file URLs when enable_file_requests_with_root is not set' do
css_path = File.join(sample_css_dir, 'import_nested.css')
document = Prawn::SVG::Document.new(svg_with_import(css_path), bounds, { enable_web_requests: false })
expect(document.warnings).to include(/Failed to load @import CSS.*No handler available/)
end
it 'does not load @import files outside the root path' do
other_dir = File.expand_path('../../sample_ttf', __dir__)
css_path = File.join(sample_css_dir, 'import_nested.css')
document = Prawn::SVG::Document.new(
svg_with_import(css_path), bounds,
{ enable_file_requests_with_root: other_dir, enable_web_requests: false }
)
expect(document.warnings).to include(/Failed to load @import CSS.*not inside the root path/)
end
it 'does not load @import web URLs when enable_web_requests is false' do
expect(Net::HTTP).not_to receive(:new)
document = Prawn::SVG::Document.new(
svg_with_import('https://invalid.test/styles.css'), bounds,
{ enable_web_requests: false }
)
expect(document.warnings).to include(/Failed to load @import CSS.*No handler available/)
end
end
context 'when the user passes in a filename instead of SVG data' do
let(:svg) { 'some_file.svg' }
it "raises an exception letting them know what they've done" do
message = "The data supplied is not a valid SVG document. It looks like you've supplied a filename " \
'instead; use File.read(filename) to get the data from the file before you pass it to prawn-svg.'
expect do
Prawn::SVG::Document.new(svg, bounds, options)
end.to raise_error Prawn::SVG::Document::InvalidSVGData, /\A#{Regexp.escape(message)}/
end
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/elements/ 0000775 0000000 0000000 00000000000 15155455534 0022073 5 ustar 00root root 0000000 0000000 mogest-prawn-svg-98d5f7a/spec/prawn/svg/elements/base_spec.rb 0000664 0000000 0000000 00000015141 15155455534 0024346 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Elements::Base do
let(:svg) { ' ' }
let(:document) do
Prawn::SVG::Document.new(svg, [800, 600], { enable_web_requests: false },
font_registry: Prawn::SVG::FontRegistry.new('Helvetica' => { normal: nil }))
end
let(:parent_calls) { [] }
let(:element) { Prawn::SVG::Elements::Base.new(document, document.root, parent_calls, fake_state) }
describe '#initialize' do
let(:svg) { ' ' }
it 'adds itself to the elements_by_id hash if an id attribute is supplied' do
element
expect(document.elements_by_id['hello']).to eq element
end
end
describe '#process' do
it 'calls #parse and #apply so subclasses can parse the element' do
expect(element).to receive(:parse).ordered
expect(element).to receive(:apply).ordered
element.process
end
describe 'applying calls from the standard attributes' do
let(:svg) do
<<-SVG
SVG
end
it 'appends the relevant calls' do
element.process
expect(element.base_calls).to eq [
['transformation_matrix', [2, 0, 0, 2, 0, 0], {}, [
['transparent', [0.5, 1], {}, [
['fill_color', ['ff0000'], {}, []],
['stroke_color', ['0000ff'], {}, []],
['line_width', [5.0], {}, []],
['fill_and_stroke', [], {}, []]
]]
]]
]
end
end
describe 'fills and strokes' do
before { element.process }
subject { element.base_calls.last }
context 'with neither fill nor stroke' do
let(:svg) { ' ' }
it { is_expected.to eq ['end_path', [], {}, []] }
end
context 'with a fill only' do
let(:svg) { ' ' }
it { is_expected.to eq ['fill', [], {}, []] }
end
context 'with a stroke only' do
let(:svg) { ' ' }
it { is_expected.to eq ['stroke', [], {}, []] }
end
context 'with fill and stroke' do
let(:svg) { ' ' }
it { is_expected.to eq ['fill_and_stroke', [], {}, []] }
end
context 'with fill with evenodd fill rule' do
let(:svg) { ' ' }
it { is_expected.to eq ['fill', [], { fill_rule: :even_odd }, []] }
end
end
it 'appends calls to the parent element' do
expect(element).to receive(:apply) do
element.send :add_call, 'test', 'argument', kw: 'argument'
end
element.process
expect(element.parent_calls).to eq [['fill', [], {}, [['test', ['argument'], { kw: 'argument' }, []]]]]
end
it 'quietly absorbs a SkipElementQuietly exception' do
expect(element).to receive(:parse).and_raise(Prawn::SVG::Elements::Base::SkipElementQuietly)
expect(element).to_not receive(:apply)
element.process
expect(document.warnings).to be_empty
end
it 'absorbs a SkipElementError exception, logging a warning' do
expect(element).to receive(:parse).and_raise(Prawn::SVG::Elements::Base::SkipElementError, 'hello')
expect(element).to_not receive(:apply)
element.process
expect(document.warnings).to eq ['hello']
end
end
describe '#apply_colors' do
before do
element.send(:extract_attributes_and_properties)
end
subject { element.send :apply_colors }
it "doesn't change anything if no fill attribute provided" do
expect(element).to_not receive(:add_call)
subject
end
it "doesn't change anything if 'inherit' fill attribute provided" do
element.properties.fill = 'inherit'
expect(element).to_not receive(:add_call)
subject
end
it "doesn't change anything if 'none' fill attribute provided" do
element.properties.fill = Prawn::SVG::Paint.parse('none')
expect(element).to_not receive(:add_call)
subject
end
it "uses the fill attribute's color" do
expect(element).to receive(:add_call).with('fill_color', 'ff0000')
element.properties.fill = Prawn::SVG::Paint.parse('red')
subject
end
it "ignores the instruction if the fill attribute's color is unparseable" do
element.properties.fill = Prawn::SVG::Paint.parse('blarble')
expect(element).to_not receive(:add_call)
subject
end
it "uses the color attribute if 'currentColor' fill attribute provided" do
element.properties.fill = Prawn::SVG::Paint.parse('currentColor')
element.state.computed_properties.color = Prawn::SVG::Color::RGB.new('ff0000')
expect(element).to receive(:add_call).with('fill_color', 'ff0000')
subject
end
context 'with a color attribute defined on a parent element' do
let(:svg) do
' '
end
let(:element) { Prawn::SVG::Elements::Root.new(document, document.root, parent_calls) }
let(:flattened_calls) { flatten_calls(element.base_calls) }
it "uses the parent's color element if 'currentColor' fill attribute provided" do
element.process
expect(flattened_calls).to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).not_to include ['fill_color', ['00ff00'], {}]
end
end
it 'specifies no color if the URL is unresolvable' do
expect(element).to_not receive(:add_call)
element.properties.fill = Prawn::SVG::Paint.parse('url(bad)')
subject
end
end
describe 'stylesheets' do
let(:svg) { <<-SVG }
SVG
it 'applies stylesheet styling but style attributes take precedence' do
document = Prawn::SVG::Document.new(svg, [100, 100], { enable_web_requests: false })
calls = []
element = Prawn::SVG::Elements::Root.new(document, document.root, calls)
element.process
fill_colors = calls.select { |cmd, _, _| cmd == 'fill_color' }.map { |_, args, _| args.first }
expect(fill_colors).to eq ['000000', 'ff0000', '008000', 'ffff00']
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/elements/gradient_spec.rb 0000664 0000000 0000000 00000017451 15155455534 0025237 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Elements::Gradient do
let(:document) { Prawn::SVG::Document.new(svg, [800, 600], { width: 800, height: 600, enable_web_requests: false }) }
let(:root_element) { Prawn::SVG::Elements::Root.new(document, document.root, []) }
let(:element) { document.gradients['flag'] }
before do
root_element.process
end
describe 'object bounding box with linear gradient' do
let(:svg) do
<<-SVG
SVG
end
it 'is stored in the document gradients table' do
expect(document.gradients['flag']).to eq element
end
it 'returns correct gradient arguments for an element with no bounding box' do
arguments = element.gradient_arguments(double(bounding_box: nil, stroke_width: 0))
expect(arguments).to eq(
from: [0.0, 0.0],
to: [0.2, 1.0],
wrap: :pad,
matrix: Matrix[[1.0, 0.0, 0.0], [0.0, -1.0, 600.0], [0.0, 0.0, 1.0]],
bounding_box: nil,
stops: [
{ offset: 0, color: 'ff0000', opacity: 1.0 },
{ offset: 0.25, color: 'ff0000', opacity: 1.0 },
{ offset: 0.5, color: 'ffffff', opacity: 1.0 },
{ offset: 0.75, color: '0000ff', opacity: 1.0 },
{ offset: 1, color: '0000ff', opacity: 1.0 }
]
)
end
it 'returns correct gradient arguments for an element' do
arguments = element.gradient_arguments(double(bounding_box: [100, 100, 200, 0], stroke_width: 0))
expect(arguments).to eq(
from: [0.0, 0.0],
to: [0.2, 1.0],
wrap: :pad,
matrix: Matrix[[100.0, 0.0, 100.0], [0.0, -100.0, 100.0], [0.0, 0.0, 1.0]],
bounding_box: [100, 100, 200, 0],
stops: [
{ offset: 0, color: 'ff0000', opacity: 1.0 },
{ offset: 0.25, color: 'ff0000', opacity: 1.0 },
{ offset: 0.5, color: 'ffffff', opacity: 1.0 },
{ offset: 0.75, color: '0000ff', opacity: 1.0 },
{ offset: 1, color: '0000ff', opacity: 1.0 }
]
)
end
end
describe 'object bounding box with radial gradient' do
let(:svg) do
<<-SVG
SVG
end
it 'is stored in the document gradients table' do
expect(document.gradients['flag']).to eq element
end
it 'returns correct gradient arguments for an element' do
arguments = element.gradient_arguments(double(bounding_box: [100, 100, 200, 0], stroke_width: 0))
expect(arguments).to eq(
from: [0.5, 0.2],
to: [0.0, 0.2],
r1: 0,
r2: 0.8,
wrap: :pad,
matrix: Matrix[[100.0, 0.0, 100.0], [0.0, -100.0, 100.0], [0.0, 0.0, 1.0]],
bounding_box: [100, 100, 200, 0],
stops: [
{ offset: 0, color: 'ff0000', opacity: 1.0 },
{ offset: 0.25, color: 'ff0000', opacity: 1.0 },
{ offset: 0.5, color: 'ffffff', opacity: 1.0 },
{ offset: 0.75, color: '0000ff', opacity: 1.0 },
{ offset: 1, color: '0000ff', opacity: 1.0 }
]
)
end
end
describe 'user space on use with linear gradient' do
let(:svg) do
<<-SVG
SVG
end
it 'returns correct gradient arguments for an element' do
arguments = element.gradient_arguments(double(bounding_box: [100, 100, 200, 0], stroke_width: 0))
expect(arguments).to eq(
from: [100.0, 500.0],
to: [200.0, 600.0],
stops: [{ offset: 0, color: 'ff0000', opacity: 1.0 }, { offset: 1, color: '0000ff', opacity: 1.0 }],
matrix: Matrix[[1.0, 0.0, 0.0], [0.0, -1.0, 600.0], [0.0, 0.0, 1.0]],
wrap: :pad,
bounding_box: [100, 100, 200, 0]
)
end
end
describe 'user space on use with radial gradient' do
let(:svg) do
<<-SVG
SVG
end
it 'returns correct gradient arguments for an element' do
arguments = element.gradient_arguments(double(bounding_box: [100, 100, 200, 0], stroke_width: 0))
expect(arguments).to eq(
from: [100.0, 500.0],
to: [200.0, 600.0],
r1: 0,
r2: 150.0,
stops: [{ offset: 0, color: 'ff0000', opacity: 1.0 }, { offset: 1, color: '0000ff', opacity: 1.0 }],
matrix: Matrix[[1.0, 0.0, 0.0], [0.0, -1.0, 600.0], [0.0, 0.0, 1.0]],
wrap: :pad,
bounding_box: [100, 100, 200, 0]
)
end
end
context 'when gradientTransform is specified' do
let(:svg) do
<<-SVG
SVG
end
it 'passes in the transform via the apply_transformations option' do
arguments = element.gradient_arguments(double(bounding_box: [100, 100, 200, 0], stroke_width: 0))
expect(arguments).to eq(
from: [0.0, 0.0],
to: [1.0, 1.0],
stops: [{ offset: 0, color: 'ff0000', opacity: 1.0 }, { offset: 1, color: '0000ff', opacity: 1.0 }],
matrix: Matrix[[200.0, 0.0, 150.0], [0.0, -200.0, 100.0], [0.0, 0.0, 1.0]],
wrap: :pad,
bounding_box: [100, 100, 200, 0]
)
end
end
context 'when a gradient is linked to another' do
let(:svg) do
<<-SVG
SVG
end
it 'correctly inherits the attributes from the parent element' do
arguments = document.gradients['flag-2'].gradient_arguments(double(bounding_box: [100, 100, 200, 0], stroke_width: 0))
expect(arguments).to eq(
from: [150.0, 500.0],
to: [220.0, 600.0],
stops: [{ offset: 0, color: 'ff0000', opacity: 1.0 }, { offset: 1, color: '0000ff', opacity: 1.0 }],
matrix: Matrix[[1.0, 0.0, 0.0], [0.0, -1.0, 600.0], [0.0, 0.0, 1.0]],
wrap: :pad,
bounding_box: [100, 100, 200, 0]
)
arguments = document.gradients['flag-3'].gradient_arguments(double(bounding_box: [100, 100, 200, 0], stroke_width: 0))
expect(arguments).to eq(
from: [170.0, 500.0],
to: [220.0, 600.0],
stops: [{ offset: 0, color: 'ff0000', opacity: 1.0 }, { offset: 1, color: '0000ff', opacity: 1.0 }],
matrix: Matrix[[1.0, 0.0, 0.0], [0.0, -1.0, 600.0], [0.0, 0.0, 1.0]],
wrap: :pad,
bounding_box: [100, 100, 200, 0]
)
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/elements/image_spec.rb 0000664 0000000 0000000 00000013056 15155455534 0024521 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Elements::Image do
let(:document) { Prawn::SVG::Document.new(svg, [800, 600], options) }
let(:element) { Prawn::SVG::Elements::Root.new(document, document.root, []) }
let(:options) { { enable_web_requests: false, enable_file_requests_with_root: '.' } }
let(:flattened_calls) { flatten_calls(element.base_calls) }
let(:external_svg) do
<<~SVG
SVG
end
describe 'view element fragment' do
let(:svg) do
<<~SVG
SVG
end
before do
document.url_loader.add_to_cache('http://image-test.invalid/drawing.svg', external_svg)
element.process
end
it 'renders without warnings' do
expect(document.warnings).to be_empty
end
it 'applies the view viewBox to the sub-document' do
sub_doc_call = flattened_calls.find { |c| c[0] == 'svg:render_sub_document' }
expect(sub_doc_call).not_to be_nil
sub_doc = sub_doc_call[1].first
expect(sub_doc.sizing.viewport_width).to eq 100.0
expect(sub_doc.sizing.viewport_height).to eq 100.0
end
end
describe 'view element with preserveAspectRatio' do
let(:svg) do
<<~SVG
SVG
end
before do
document.url_loader.add_to_cache('http://image-test.invalid/drawing.svg', external_svg)
element.process
end
it 'renders without warnings' do
expect(document.warnings).to be_empty
end
end
describe 'svgView() fragment' do
let(:svg) do
<<~SVG
SVG
end
before do
document.url_loader.add_to_cache('http://image-test.invalid/drawing.svg', external_svg)
element.process
end
it 'renders without warnings' do
expect(document.warnings).to be_empty
end
it 'applies the svgView viewBox to the sub-document' do
sub_doc_call = flattened_calls.find { |c| c[0] == 'svg:render_sub_document' }
expect(sub_doc_call).not_to be_nil
sub_doc = sub_doc_call[1].first
expect(sub_doc.sizing.viewport_width).to eq 100.0
expect(sub_doc.sizing.viewport_height).to eq 100.0
expect(sub_doc.sizing.x_offset).to eq(50.0)
expect(sub_doc.sizing.y_offset).to eq(50.0)
end
end
describe 'svgView() with preserveAspectRatio' do
let(:svg) do
<<~SVG
SVG
end
before do
document.url_loader.add_to_cache('http://image-test.invalid/drawing.svg', external_svg)
element.process
end
it 'renders without warnings' do
expect(document.warnings).to be_empty
end
end
describe 'fragment referencing non-view element' do
let(:svg) do
<<~SVG
SVG
end
before do
document.url_loader.add_to_cache('http://image-test.invalid/drawing.svg', external_svg)
element.process
end
it 'renders the full SVG without applying any view override' do
expect(flattened_calls.any? { |c| c[0] == 'svg:render_sub_document' }).to be true
end
end
describe 'view element in document does not produce warnings' do
let(:svg) do
<<~SVG
SVG
end
before { element.process }
it 'renders without warnings' do
expect(document.warnings).to be_empty
end
end
describe 'URL without fragment' do
let(:svg) do
<<~SVG
SVG
end
before do
document.url_loader.add_to_cache('http://image-test.invalid/drawing.svg', external_svg)
element.process
end
it 'renders the full SVG normally' do
expect(flattened_calls.any? { |c| c[0] == 'svg:render_sub_document' }).to be true
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/elements/line_spec.rb 0000664 0000000 0000000 00000002744 15155455534 0024370 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::Elements::Line do
let(:document) { Prawn::SVG::Document.new(svg, [800, 600], { width: 800, height: 600, enable_web_requests: false }) }
subject do
Prawn::SVG::Elements::Line.new(document, document.root, [], fake_state)
end
context 'with attributes specified' do
let(:svg) { ' ' }
it 'renders the line' do
subject.process
expect(subject.base_calls).to eq [
['stroke_color', ['000000'], {}, []],
['stroke', [], {}, [
['move_to', [[5.0, 590.0]], {}, []],
['line_to', [[15.0, 580.0]], {}, []]
]]
]
end
end
context 'with no attributes nor stroke specified' do
let(:svg) { ' ' }
it 'outlines a path from 0,0 to 0,0' do
subject.process
expect(subject.base_calls).to eq [
['end_path', [], {}, [
['move_to', [[0, 600]], {}, []],
['line_to', [[0, 600]], {}, []]
]]
]
end
end
context 'with a fill specified' do
let(:svg) { ' ' }
it 'ignores the fill' do
subject.process
expect(subject.base_calls).to eq [
['fill_color', ['0000ff'], {}, []],
['stroke_color', ['ff0000'], {}, []],
['stroke', [], {}, [
['move_to', [[0, 600]], {}, []],
['line_to', [[15.0, 580.0]], {}, []]
]]
]
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/elements/marker_spec.rb 0000664 0000000 0000000 00000005504 15155455534 0024717 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::Elements::Marker do
let(:svg) do
<<-SVG
SVG
end
let(:document) { Prawn::SVG::Document.new(svg, [800, 600], { width: 800, height: 600, enable_web_requests: false }) }
def new_state
state = Prawn::SVG::State.new
state.viewport_sizing = document.sizing
state
end
let(:line_element) do
Prawn::SVG::Elements::Line.new(document, document.root.elements[2], [], new_state)
end
subject do
Prawn::SVG::Elements::Marker.new(document, document.root.elements[1], [], new_state)
end
describe '#parse' do
it 'forces display none' do
subject.parse
expect(subject.properties.display).to eq 'none'
end
end
describe '#apply_marker' do
it 'adds the line and its marker to the call stack' do
subject.process
line_element.process
# We didn't use a marker-* attribute on the tag, that's
# why the apply_marker method wasn't automatically called as part
# of the line_element.process call above.
subject.apply_marker(line_element, point: [10, 10], angle: 45)
# This example follows the example in the SVG 1.1 documentation
# in section 11.6.3.
expect(line_element.base_calls).to eq [
['stroke_color', ['000000'], {}, []],
['line_width', [100.0], {}, []],
['stroke', [], {}, [
['move_to', [[0.0, 600.0]], {}, []],
['line_to', [[10.0, 590.0]], {}, []]
]],
['save', [], {}, []],
['transformation_matrix', [1, 0, 0, 1, 10, -10], {}, []],
['rotate', [-45], { origin: [0, 600.0] }, [
['transformation_matrix', [100.0, 0, 0, 100.0, 0, 0], {}, []],
['transformation_matrix', [1, 0, 0, 1, -0.0, 1.5], {}, []],
['rectangle', [[-0.5, 600.0], 4.0, 3.0], {}, []],
['clip', [], {}, []],
['transformation_matrix', [0.3, 0, 0, 0.3, 0, 0], {}, []],
['fill_color', ['000000'], {}, []],
['line_width', [1.0], {}, []],
['cap_style', [:butt], {}, []],
['join_style', [:miter], {}, []],
['miter_limit', [4.0], {}, []],
['undash', [], {}, []],
['save', [], {}, []],
['fill', [], {}, [
['move_to', [[0.0, 600.0]], {}, []],
['line_to', [[10.0, 595.0]], {}, []],
['line_to', [[0.0, 590.0]], {}, []],
['close_path', [], {}, []]
]],
['restore', [], {}, []]
]],
['restore', [], {}, []]
]
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/elements/mask_spec.rb 0000664 0000000 0000000 00000026732 15155455534 0024377 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Elements::Mask do
let(:document) { Prawn::SVG::Document.new(svg, [800, 600], { enable_web_requests: false }) }
let(:element) { Prawn::SVG::Elements::Root.new(document, document.root, []) }
let(:flattened_calls) { flatten_calls(element.base_calls) }
def find_call(calls, name)
calls.each do |call|
return call if call[0] == name
result = find_call(call[3], name)
return result if result
end
nil
end
def find_all_calls(calls, name, results = [])
calls.each do |call|
results << call if call[0] == name
find_all_calls(call[3], name, results)
end
results
end
before { element.process }
describe 'mask element' do
let(:svg) do
<<~SVG
SVG
end
it 'generates save_graphics_state and soft_mask calls' do
expect(flattened_calls).to include ['save_graphics_state', [], {}]
expect(flattened_calls).to include ['soft_mask', [], {}]
end
it 'renders the masked element with fill color' do
expect(flattened_calls).to include ['fill_color', ['ff0000'], {}]
end
end
describe 'display none behavior' do
let(:svg) do
<<~SVG
SVG
end
it 'does not render mask content directly' do
mask_element = document.elements_by_id['m1']
expect(mask_element.computed_properties.display).to eq 'none'
end
end
describe 'container behavior' do
let(:svg) do
<<~SVG
SVG
end
it 'acts as a container element' do
mask_element = document.elements_by_id['m1']
expect(mask_element).to be_a Prawn::SVG::Elements::Mask
end
end
describe 'missing mask reference' do
let(:svg) do
<<~SVG
SVG
end
it 'emits a warning' do
expect(document.warnings).to include 'Could not resolve mask URI to a mask element'
end
end
describe 'mask="none"' do
let(:svg) do
<<~SVG
SVG
end
it 'does not emit warnings or soft_mask calls' do
expect(document.warnings).to be_empty
expect(flattened_calls).not_to include ['soft_mask', [], {}]
end
end
describe 'maskContentUnits=objectBoundingBox without bounding box' do
let(:svg) do
<<~SVG
SVG
end
it 'emits a warning about missing bounding box' do
expect(document.warnings).to include 'mask with maskContentUnits="objectBoundingBox" requires element to have a bounding box'
end
end
describe 'maskUnits=userSpaceOnUse' do
let(:svg) do
<<~SVG
SVG
end
it 'renders the mask with clip region' do
expect(flattened_calls).to include ['save_graphics_state', [], {}]
expect(flattened_calls).to include ['soft_mask', [], {}]
expect(flattened_calls).to include ['clip', [], {}]
end
end
describe 'objectBoundingBox mask region clipping' do
let(:svg) do
<<~SVG
SVG
end
it 'includes a clip call for the mask region' do
expect(flattened_calls).to include ['clip', [], {}]
end
end
describe 'maskContentUnits=objectBoundingBox with coordinate scaling' do
let(:svg) do
<<~SVG
SVG
end
it 'scales mask content coordinates to the element bounding box' do
soft_mask_call = find_call(element.base_calls, 'soft_mask')
expect(soft_mask_call).not_to be_nil
# The soft_mask children include a clip rectangle (1.2x expanded) followed by
# the scaled content. Find rectangles and check the content one (second).
mask_children = soft_mask_call[3]
rects = find_all_calls(mask_children, 'rectangle')
expect(rects.length).to eq 2
# First rect is the clip region (150 * 1.2 = 180, 100 * 1.2 = 120)
_, clip_w, clip_h = rects[0][1]
expect(clip_w).to be_within(0.01).of(180)
expect(clip_h).to be_within(0.01).of(120)
# Second rect is the scaled mask content (1.0 * 150 = 150, 1.0 * 100 = 100)
point, content_w, content_h = rects[1][1]
expect(content_w).to be_within(0.01).of(150)
expect(content_h).to be_within(0.01).of(100)
expect(point[0]).to be_within(0.01).of(20)
end
end
describe 'mask applied to a group' do
let(:svg) do
<<~SVG
SVG
end
it 'generates soft_mask around the group contents' do
expect(flattened_calls).to include ['soft_mask', [], {}]
expect(flattened_calls).to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).to include ['fill_color', ['0000ff'], {}]
end
end
describe 'multiple elements sharing the same mask' do
let(:svg) do
<<~SVG
SVG
end
it 'generates separate soft_mask calls for each element' do
soft_mask_calls = flattened_calls.select { |c| c == ['soft_mask', [], {}] }
expect(soft_mask_calls.length).to eq 2
end
end
describe 'nested content inside mask' do
let(:svg) do
<<~SVG
SVG
end
it 'includes mask content from nested elements' do
expect(flattened_calls).to include ['soft_mask', [], {}]
expect(flattened_calls.any? { |c| c[0] == 'rectangle' }).to be true
expect(flattened_calls.any? { |c| c[0] == 'circle' }).to be true
end
end
describe 'mask with gradient fill' do
let(:svg) do
<<~SVG
SVG
end
it 'includes gradient rendering calls in the mask' do
expect(flattened_calls).to include ['soft_mask', [], {}]
expect(flattened_calls.any? { |c| c[0] == 'svg:render_gradient' }).to be true
end
end
describe 'maskContentUnits=objectBoundingBox with rounded_rectangle' do
let(:svg) do
<<~SVG
SVG
end
it 'scales rounded_rectangle coordinates to the element bounding box' do
soft_mask_call = find_call(element.base_calls, 'soft_mask')
expect(soft_mask_call).not_to be_nil
mask_children = soft_mask_call[3]
rounded_rects = find_all_calls(mask_children, 'rounded_rectangle')
expect(rounded_rects.length).to eq 1
point, width, height, radius = rounded_rects[0][1]
expect(width).to be_within(0.01).of(150)
expect(height).to be_within(0.01).of(100)
expect(radius).to be_within(0.01).of(15)
expect(point[0]).to be_within(0.01).of(20)
end
end
describe 'maskUnits=userSpaceOnUse default clip coordinates' do
let(:svg) do
<<~SVG
SVG
end
it 'uses -10%/120% defaults producing correct Prawn coordinates' do
soft_mask_call = find_call(element.base_calls, 'soft_mask')
expect(soft_mask_call).not_to be_nil
mask_children = soft_mask_call[3]
rects = find_all_calls(mask_children, 'rectangle')
clip_rect = rects.first
point, width, height = clip_rect[1]
expect(point[0]).to be_within(0.01).of(-20)
expect(point[1]).to be_within(0.01).of(220)
expect(width).to be_within(0.01).of(240)
expect(height).to be_within(0.01).of(240)
end
end
describe 'mask referencing non-mask element' do
let(:svg) do
<<~SVG
SVG
end
it 'emits a warning' do
expect(document.warnings).to include 'Could not resolve mask URI to a mask element'
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/elements/path_spec.rb 0000664 0000000 0000000 00000020726 15155455534 0024375 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Elements::Path do
let(:source) { double(name: 'path', attributes: {}) }
let(:state) { Prawn::SVG::State.new }
let(:path) { Prawn::SVG::Elements::Path.new(nil, source, [], state) }
before do
allow(path).to receive(:attributes).and_return('d' => d)
end
describe 'command parsing' do
context 'with a valid path' do
let(:d) { 'm12.34 -56.78 1 2M4 5 12-34 -.5.7+3 2.3e3 4e4 4e+4 L31,-2e-5L 6,7 Z ZZa50 50 0 100 100' }
it 'correctly parses' do
calls = []
allow(path).to receive(:parse_path_command) { |*args| calls << args }
path.parse
expect(calls).to eq [
['m', [[12.34, -56.78], [1, 2]]],
['M', [[4, 5], [12, -34], [-0.5, 0.7], [3, 2.3e3], [4e4, 4e4]]],
['L', [[31, -2e-5]]],
['L', [[6, 7]]],
['Z', []],
['Z', []],
['Z', []],
['a', [[50, 50, 0, 1, 0, 0, 100]]]
]
end
end
context 'with m and M commands' do
let(:d) { 'M 1,2 3,4 m 5,6 7,8' }
it 'treats subsequent points to m/M command as relative/absolute depending on command' do
[
['M', [[1, 2], [3, 4]]],
['L', [[3, 4]]],
['m', [[5, 6], [7, 8]]],
['l', [[7, 8]]]
].each do |args|
expect(path).to receive(:parse_path_command).with(*args).and_call_original
end
path.parse
end
end
context 'with an empty path' do
let(:d) { '' }
it 'correctly parses' do
expect(path).not_to receive(:run_path_command)
path.parse
end
end
context 'with a path with invalid characters' do
let(:d) { 'M 10 % 20' }
it 'raises' do
expect { path.parse }.to raise_error(Prawn::SVG::Elements::Base::SkipElementError)
end
end
context 'with a path with numerical data before a command letter' do
let(:d) { 'M 10 % 20' }
it 'raises' do
expect { path.parse }.to raise_error(Prawn::SVG::Elements::Base::SkipElementError)
end
end
end
context 'when given an M path' do
subject do
path.parse
path.commands
end
context 'with typical arguments' do
let(:d) { 'M 100 200 M 200 300 m 10 20' }
it 'issues a move command' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 200.0]),
Prawn::SVG::Elements::Path::Move.new([200.0, 300.0]),
Prawn::SVG::Elements::Path::Move.new([210.0, 320.0])
]
end
end
context 'with only one argument' do
let(:d) { 'M 100 200 M 100' }
it 'bails out' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 200.0])
]
end
end
context 'with no arguments' do
let(:d) { 'M 100 200 M' }
it 'bails out' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 200.0])
]
end
end
end
context 'when given an L path' do
subject do
path.parse
path.commands
end
context 'with typical arguments' do
let(:d) { 'M 100 200 L 200 300 l 10 20' }
it 'issues a line command' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 200.0]),
Prawn::SVG::Elements::Path::Line.new([200.0, 300.0]),
Prawn::SVG::Elements::Path::Line.new([210.0, 320.0])
]
end
end
context 'with only one argument' do
let(:d) { 'M 100 200 L 100' }
it 'bails out' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 200.0])
]
end
end
end
context 'when given a C path' do
subject do
path.parse
path.commands
end
context 'with typical arguments' do
let(:d) { 'M 100 200 C 10 20 30 40 200 300' }
it 'issues a curve command' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 200.0]),
Prawn::SVG::Elements::Path::Curve.new([200.0, 300.0], [10, 20], [30, 40])
]
end
end
context 'with incomplete arguments' do
let(:d) { 'M 100 200 C 10 20 30 40 50' }
it 'bails out' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 200.0])
]
end
end
end
context 'when given an S path' do
subject do
path.parse
path.commands
end
context 'with typical arguments' do
let(:d) { 'M 100 200 S 30 40 200 300' }
it 'issues a curve command' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 200.0]),
Prawn::SVG::Elements::Path::Curve.new([200.0, 300.0], [100, 200], [30, 40])
]
end
end
context 'with incomplete arguments' do
let(:d) { 'M 100 200 S 30 40 50' }
it 'bails out' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 200.0])
]
end
end
end
context 'when given a Q path' do
subject do
path.parse
path.commands
end
context 'with typical arguments' do
let(:d) { 'M 0 0 Q 600 300 300 600' }
it 'issues a curve command' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([0, 0]),
Prawn::SVG::Elements::Path::Curve.new([300.0, 600.0], [400, 200], [500, 400])
]
end
end
context 'with incomplete arguments' do
let(:d) { 'M 100 200 Q 30 40 50' }
it 'bails out' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 200.0])
]
end
end
end
context 'when given a T path' do
subject do
path.parse
path.commands
end
context 'with typical arguments' do
let(:d) { 'M 0 0 T 300 600' }
it 'issues a curve command' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([0, 0]),
Prawn::SVG::Elements::Path::Curve.new([300.0, 600.0], [0, 0], [100, 200])
]
end
end
context 'with incomplete arguments' do
let(:d) { 'M 100 200 T 30' }
it 'bails out' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 200.0])
]
end
end
end
context 'when given an A path' do
subject do
path.parse
path.commands
end
context 'that is pretty normal' do
let(:d) { 'M 100 200 A 10 10 0 0 1 200 200' }
it 'uses bezier curves to approximate an arc path' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 200.0]),
Prawn::SVG::Elements::Path::Curve.new([150.0, 150.0], [100.0, 172.57081148225683],
[122.57081148225683, 150.0]),
Prawn::SVG::Elements::Path::Curve.new([200.0, 200.0], [177.42918851774317, 150.0],
[200.0, 172.57081148225683])
]
end
end
context 'with an identical start and end point' do
let(:d) { 'M 100 200 A 30 30 0 0 1 100 200' }
it 'ignores the path' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 200.0])
]
end
end
context 'with an rx of 0' do
let(:d) { 'M 100 200 A 0 10 0 0 1 200 200' }
it 'substitutes a line_to' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 200.0]),
Prawn::SVG::Elements::Path::Line.new([200.0, 200.0])
]
end
end
context 'with an ry of 0' do
let(:d) { 'M 100 200 A 10 0 0 0 1 200 200' }
it 'substitutes a line_to' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 200.0]),
Prawn::SVG::Elements::Path::Line.new([200.0, 200.0])
]
end
end
context 'with incomplete arguments' do
let(:d) { 'M 100 200 A 10 20 30 L 10 20' }
it 'bails out' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 200.0])
]
end
end
context 'with highly-compressed flags' do
let(:d) { 'M100,100a50 50 0 100 100' }
it 'correctly parses them' do
expect(subject).to eq [
Prawn::SVG::Elements::Path::Move.new([100.0, 100.0]),
Prawn::SVG::Elements::Path::Curve.new([50.0, 150.0], [72.57081148225683, 100.0], [50.0, 122.57081148225683]),
Prawn::SVG::Elements::Path::Curve.new([99.99999999999999, 200.0], [50.0, 177.42918851774317],
[72.57081148225681, 200.0])
]
end
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/elements/pattern_spec.rb 0000664 0000000 0000000 00000017037 15155455534 0025117 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Elements::Pattern do
let(:document) { Prawn::SVG::Document.new(svg, [800, 600], { width: 800, height: 600, enable_web_requests: false }) }
let(:root_element) { Prawn::SVG::Elements::Root.new(document, document.root, []) }
let(:element) { document.gradients['pat'] }
before do
root_element.process
end
describe 'userSpaceOnUse pattern' do
let(:svg) do
<<-SVG
SVG
end
it 'is stored in the document gradients table' do
expect(element).to be_a(Prawn::SVG::Elements::Pattern)
end
it 'returns pattern arguments with correct tile geometry' do
mock_element = double(bounding_box: [0, 600, 200, 500], stroke_width: 0)
args = element.pattern_arguments(mock_element)
expect(args[:tile_x]).to eq 10.0
expect(args[:tile_y]).to eq 550.0 # output_height(600) - svg_y(20) - height(30)
expect(args[:tile_width]).to eq 50.0
expect(args[:tile_height]).to eq 30.0
expect(args[:transform]).to eq Matrix.identity(3)
expect(args[:calls]).to be_an(Array)
expect(args[:calls]).not_to be_empty
end
it 'returns nil when width is zero' do
allow(element).to receive(:derive_attribute).and_call_original
allow(element).to receive(:derive_attribute).with('width').and_return('0')
mock_element = double(bounding_box: [0, 600, 200, 500], stroke_width: 0)
expect(element.pattern_arguments(mock_element)).to be_nil
end
end
describe 'objectBoundingBox pattern' do
let(:svg) do
<<-SVG
SVG
end
it 'computes tile geometry relative to the bounding box' do
# bbox in Prawn coords: [left=100, top=500, right=300, bottom=400]
bbox = [100, 500, 300, 400]
mock_element = double(bounding_box: bbox, stroke_width: 0)
args = element.pattern_arguments(mock_element)
# bbox_w = 200, bbox_h = 100
# tile_w = 0.5 * 200 = 100
# tile_h = 0.25 * 100 = 25
# tile_x = 100 + 0.1 * 200 = 120
# tile_y_top = 500 - 0.2 * 100 = 480
# tile_y_bottom = 480 - 25 = 455
expect(args[:tile_x]).to eq 120.0
expect(args[:tile_y]).to eq 455.0
expect(args[:tile_width]).to eq 100.0
expect(args[:tile_height]).to eq 25.0
end
it 'returns nil when bounding box is nil' do
mock_element = double(bounding_box: nil, stroke_width: 0)
expect(element.pattern_arguments(mock_element)).to be_nil
end
end
describe 'patternTransform' do
let(:svg) do
<<-SVG
SVG
end
it 'includes the transform matrix' do
mock_element = double(bounding_box: [0, 600, 200, 500], stroke_width: 0)
args = element.pattern_arguments(mock_element)
expect(args[:transform]).not_to eq Matrix.identity(3)
end
end
describe 'viewBox' do
let(:svg) do
<<-SVG
SVG
end
it 'includes viewBox transform calls in content' do
mock_element = double(bounding_box: [0, 600, 200, 500], stroke_width: 0)
args = element.pattern_arguments(mock_element)
# Should have transformation_matrix calls for viewBox scaling
transform_calls = args[:calls].select { |c| c[0] == 'transformation_matrix' }
expect(transform_calls).not_to be_empty
end
end
describe 'href inheritance' do
let(:svg) do
<<-SVG
SVG
end
it 'inherits attributes from the parent pattern' do
expect(element.derive_attribute('width')).to eq '40'
expect(element.derive_attribute('height')).to eq '40'
expect(element.derive_attribute('patternUnits')).to eq 'userSpaceOnUse'
end
it 'overrides attributes specified on the child' do
expect(element.derive_attribute('x')).to eq '10'
expect(element.derive_attribute('y')).to eq '10'
end
it 'inherits content from parent when child has none' do
mock_element = double(bounding_box: [0, 600, 200, 500], stroke_width: 0)
args = element.pattern_arguments(mock_element)
expect(args[:calls]).not_to be_empty
end
end
describe 'objectBoundingBox content units' do
let(:svg) do
<<-SVG
SVG
end
it 'returns content calls scaled to the bounding box' do
bbox = [100, 500, 300, 400]
mock_element = double(bounding_box: bbox, stroke_width: 0)
args = element.pattern_arguments(mock_element)
expect(args[:calls]).not_to be_empty
end
it 'returns nil when bounding box is nil' do
mock_element = double(bounding_box: nil, stroke_width: 0)
expect(element.pattern_arguments(mock_element)).to be_nil
end
end
describe 'pattern without id' do
let(:svg) do
<<-SVG
SVG
end
it 'is silently skipped' do
expect(document.gradients['anything']).to be_nil
end
end
describe 'integration with fill' do
let(:svg) do
<<-SVG
SVG
end
it 'generates svg:render_pattern calls' do
calls = root_element.base_calls.flatten(1)
calls.find { |c| c.is_a?(Array) && c[0] == 'svg:render_pattern' }
# The pattern call should exist somewhere in the call tree
found = find_call(root_element.base_calls, 'svg:render_pattern')
expect(found).to be true
end
end
def find_call(calls, name)
calls.any? do |call_name, _args, _kwargs, children|
call_name == name || (children && find_call(children, name))
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/elements/polygon_spec.rb 0000664 0000000 0000000 00000002522 15155455534 0025122 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::Elements::Polygon do
let(:document) { Prawn::SVG::Document.new(svg, [800, 600], { width: 800, height: 600, enable_web_requests: false }) }
subject do
Prawn::SVG::Elements::Polygon.new(document, document.root, [], Prawn::SVG::State.new)
end
context 'with a valid points attribute' do
let(:svg) { ' ' }
it 'renders the polygon' do
subject.process
expect(subject.base_calls).to eq [
['fill', [], {}, [
['move_to', [[10.0, 590.0]], {}, []],
['line_to', [[20.0, 580.0]], {}, []],
['line_to', [[30.0, 570.0]], {}, []],
['close_path', [], {}, []]
]]
]
end
end
context 'with a polygon that has an odd number of arguments' do
let(:svg) { ' ' }
it 'ignores the last one' do
subject.process
expect(subject.base_calls).to eq [
['fill', [], {}, [
['move_to', [[10.0, 590.0]], {}, []],
['line_to', [[20.0, 580.0]], {}, []],
['close_path', [], {}, []]
]]
]
end
end
context 'with a polygon that has no arguments' do
let(:svg) { ' ' }
it 'renders nothing' do
subject.process
expect(subject.base_calls).to eq []
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/elements/polyline_spec.rb 0000664 0000000 0000000 00000002416 15155455534 0025270 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::Elements::Polyline do
let(:document) { Prawn::SVG::Document.new(svg, [800, 600], { width: 800, height: 600, enable_web_requests: false }) }
subject do
Prawn::SVG::Elements::Polyline.new(document, document.root, [], Prawn::SVG::State.new)
end
context 'with a valid points attribute' do
let(:svg) { ' ' }
it 'renders the polyline' do
subject.process
expect(subject.base_calls).to eq [
['fill', [], {}, [
['move_to', [[10.0, 590.0]], {}, []],
['line_to', [[20.0, 580.0]], {}, []],
['line_to', [[30.0, 570.0]], {}, []]
]]
]
end
end
context 'with a polyline that has an odd number of arguments' do
let(:svg) { ' ' }
it 'ignores the last one' do
subject.process
expect(subject.base_calls).to eq [
['fill', [], {}, [
['move_to', [[10.0, 590.0]], {}, []],
['line_to', [[20.0, 580.0]], {}, []]
]]
]
end
end
context 'with a polyline that has no arguments' do
let(:svg) { ' ' }
it 'renders nothing' do
subject.process
expect(subject.base_calls).to eq []
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/elements/root_spec.rb 0000664 0000000 0000000 00000001566 15155455534 0024425 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Elements::Root do
let(:color_mode) { :rgb }
let(:sizing) do
instance_double(Prawn::SVG::Calculators::DocumentSizing, x_offset: 0, y_offset: 0, x_scale: 1, y_scale: 1)
end
let(:document) do
instance_double(Prawn::SVG::Document, color_mode: color_mode, sizing: sizing)
end
let(:source) { double(name: 'svg', attributes: {}) }
let(:state) { Prawn::SVG::State.new }
let(:element) { Prawn::SVG::Elements::Root.new(document, source, [], state) }
it 'uses RGB black as the default color' do
element.apply
expect(element.calls.first).to eq ['fill_color', ['000000'], {}, []]
end
context 'when in CMYK mode' do
let(:color_mode) { :cmyk }
it 'uses CMYK black as the default color' do
element.apply
expect(element.calls.first).to eq ['fill_color', [[0, 0, 0, 100]], {}, []]
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/elements/switch_spec.rb 0000664 0000000 0000000 00000023512 15155455534 0024736 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Elements::Switch do
let(:options) { { enable_web_requests: false } }
let(:document) { Prawn::SVG::Document.new(svg, [800, 600], options) }
let(:element) { Prawn::SVG::Elements::Root.new(document, document.root, []) }
let(:flattened_calls) { flatten_calls(element.base_calls) }
before { element.process }
describe 'renders only the first matching child' do
let(:svg) do
<<~SVG
SVG
end
it 'renders the first child and skips the second' do
expect(flattened_calls).to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).not_to include ['fill_color', ['0000ff'], {}]
end
end
describe 'requiredFeatures' do
context 'with a supported feature' do
let(:svg) do
<<~SVG
SVG
end
it 'renders the element with the supported feature' do
expect(flattened_calls).to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).not_to include ['fill_color', ['0000ff'], {}]
end
end
context 'with an unsupported feature' do
let(:svg) do
<<~SVG
SVG
end
it 'skips the element and renders the fallback' do
expect(flattened_calls).not_to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).to include ['fill_color', ['0000ff'], {}]
end
end
context 'with multiple features, one unsupported' do
let(:svg) do
<<~SVG
SVG
end
it 'skips the element because all features must be supported' do
expect(flattened_calls).not_to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).to include ['fill_color', ['0000ff'], {}]
end
end
context 'with an empty requiredFeatures attribute' do
let(:svg) do
<<~SVG
SVG
end
it 'evaluates to false and skips the element' do
expect(flattened_calls).not_to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).to include ['fill_color', ['0000ff'], {}]
end
end
end
describe 'requiredExtensions' do
context 'with any extension specified' do
let(:svg) do
<<~SVG
SVG
end
it 'evaluates to false since no extensions are supported' do
expect(flattened_calls).not_to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).to include ['fill_color', ['0000ff'], {}]
end
end
end
describe 'systemLanguage' do
context 'with matching language' do
let(:options) { { enable_web_requests: false, language: 'en' } }
let(:svg) do
<<~SVG
SVG
end
it 'renders the element with the matching language' do
expect(flattened_calls).to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).not_to include ['fill_color', ['0000ff'], {}]
end
end
context 'with non-matching language' do
let(:options) { { enable_web_requests: false, language: 'fr' } }
let(:svg) do
<<~SVG
SVG
end
it 'skips the element and renders the fallback' do
expect(flattened_calls).not_to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).to include ['fill_color', ['0000ff'], {}]
end
end
context 'with prefix matching' do
let(:options) { { enable_web_requests: false, language: 'en' } }
let(:svg) do
<<~SVG
SVG
end
it 'matches when user language is a prefix of the element language' do
expect(flattened_calls).to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).not_to include ['fill_color', ['0000ff'], {}]
end
end
context 'with multiple languages in comma-separated list' do
let(:options) { { enable_web_requests: false, language: 'fr' } }
let(:svg) do
<<~SVG
SVG
end
it 'matches if any language in the list matches' do
expect(flattened_calls).to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).not_to include ['fill_color', ['0000ff'], {}]
end
end
context 'defaults to en when no language option provided' do
let(:svg) do
<<~SVG
SVG
end
it 'uses en as the default language' do
expect(flattened_calls).to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).not_to include ['fill_color', ['0000ff'], {}]
end
end
context 'with empty systemLanguage attribute' do
let(:svg) do
<<~SVG
SVG
end
it 'evaluates to false' do
expect(flattened_calls).not_to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).to include ['fill_color', ['0000ff'], {}]
end
end
end
describe 'fallback with no conditional attributes' do
let(:svg) do
<<~SVG
SVG
end
it 'renders the fallback element that has no conditional attributes' do
expect(flattened_calls).not_to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).to include ['fill_color', ['0000ff'], {}]
end
end
describe 'no matching children' do
let(:svg) do
<<~SVG
SVG
end
it 'renders nothing' do
expect(flattened_calls).not_to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).not_to include ['fill_color', ['0000ff'], {}]
end
end
describe 'switch with group child' do
let(:svg) do
<<~SVG
SVG
end
it 'renders the entire group subtree' do
expect(flattened_calls).to include ['fill_color', ['ff0000'], {}]
expect(flattened_calls).to include ['fill_color', ['0000ff'], {}]
end
end
describe 'case-insensitive language matching' do
let(:options) { { enable_web_requests: false, language: 'EN' } }
let(:svg) do
<<~SVG
SVG
end
it 'matches case-insensitively' do
expect(flattened_calls).to include ['fill_color', ['ff0000'], {}]
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/elements/text_path_spec.rb 0000664 0000000 0000000 00000013007 15155455534 0025433 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Elements::TextPath do
let(:document) do
Prawn::SVG::Document.new(svg, [800, 600], { enable_web_requests: false },
font_registry: Prawn::SVG::FontRegistry.new('Helvetica' => { normal: nil }, 'Times-Roman' => { normal: nil }))
end
let(:element) { Prawn::SVG::Elements::Text.new(document, document.root.elements[2], [], fake_state) }
let(:prawn) { Prawn::Document.new(margin: 0) }
let(:renderer) { Prawn::SVG::Renderer.new(prawn, document, {}) }
def process_and_render
# Process the defs first to register elements_by_id
defs = Prawn::SVG::Elements::Container.new(document, document.root.elements[1], [], fake_state)
defs.process
element.process
element.render(prawn, renderer)
end
describe 'text along a straight horizontal path' do
let(:svg) do
<<~SVG
Hello
SVG
end
it 'renders characters along the path' do
drawn = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
drawn << [text, opts[:at], opts[:rotate]]
method.call(text, **opts)
end
process_and_render
expect(drawn).not_to be_empty
expect(drawn.map(&:first).join).to eq 'Hello'
drawn.each do |_text, at, rotate|
expect(at[1]).to be_within(20).of(550)
expect(rotate).to be_within(0.1).of(0)
end
end
end
describe 'with startOffset' do
let(:svg) do
<<~SVG
Hi
SVG
end
it 'starts text at the offset distance' do
drawn = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
drawn << [text, opts[:at]]
method.call(text, **opts)
end
process_and_render
expect(drawn).not_to be_empty
# First character should start at approximately x=50 + half char width
expect(drawn.first[1][0]).to be > 40
end
end
describe 'with percentage startOffset' do
let(:svg) do
<<~SVG
A
SVG
end
it 'starts at the percentage of path length' do
drawn = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
drawn << [text, opts[:at]]
method.call(text, **opts)
end
process_and_render
expect(drawn).not_to be_empty
# 50% of 200 = 100, so first char midpoint at ~100
expect(drawn.first[1][0]).to be_within(20).of(100)
end
end
describe 'when referenced path does not exist' do
let(:svg) do
<<~SVG
Hello
SVG
end
it 'warns and skips' do
process_and_render
expect(document.warnings).to include(match(/not a path element/))
end
end
describe 'when href is missing' do
let(:svg) do
<<~SVG
Hello
SVG
end
it 'warns and skips' do
process_and_render
expect(document.warnings).to include(match(/must reference/))
end
end
describe 'characters past path end' do
let(:svg) do
<<~SVG
Hello World this is a long text
SVG
end
it 'stops rendering when past path end' do
drawn = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
drawn << text
method.call(text, **opts)
end
process_and_render
# Should render fewer characters than the full text
rendered_text = drawn.join
expect(rendered_text.length).to be < 'Hello World this is a long text'.length
end
end
describe 'text along a curved path' do
let(:svg) do
<<~SVG
Curved
SVG
end
it 'renders characters with varying angles' do
drawn = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
drawn << [text, opts[:rotate]]
method.call(text, **opts)
end
process_and_render
expect(drawn).not_to be_empty
# Characters along a curve should have different rotation angles
angles = drawn.map { |_, r| r }.compact
expect(angles.uniq.length).to be > 1 if angles.length > 1
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/elements/text_spec.rb 0000664 0000000 0000000 00000073514 15155455534 0024430 0 ustar 00root root 0000000 0000000 require "#{File.dirname(__FILE__)}/../../../spec_helper"
describe Prawn::SVG::Elements::Text do
let(:document) do
Prawn::SVG::Document.new(svg, [800, 600], { enable_web_requests: false },
font_registry: Prawn::SVG::FontRegistry.new('Helvetica' => { normal: nil }, 'Courier' => { normal: nil }, 'Times-Roman' => { normal: nil }))
end
let(:element) { Prawn::SVG::Elements::Text.new(document, document.root, [], fake_state) }
let(:prawn) { Prawn::Document.new(margin: 0) }
let(:renderer) { Prawn::SVG::Renderer.new(prawn, document, {}) }
def process_and_render
element.process
element.render(prawn, renderer)
end
describe 'basic text rendering' do
let(:svg) { 'Hello World ' }
it 'renders simple text' do
expect(prawn).to receive(:draw_text).with(anything, hash_including(:size, :at)).and_call_original
process_and_render
end
it 'lays out the text during rendering' do
allow(prawn).to receive(:width_of).and_call_original
expect(prawn).to receive(:save_font).at_least(:once).and_call_original
expect(prawn).to receive(:width_of).with('Hello World', hash_including(:kerning)).and_call_original
process_and_render
end
end
describe 'xml:space preserve' do
let(:svg) { %(some\n\t text ) }
context 'when xml:space is preserve' do
let(:attributes) { ' xml:space="preserve"' }
it 'converts newlines and tabs to spaces, and preserves spaces' do
allow(prawn).to receive(:width_of).and_call_original
allow(prawn).to receive(:draw_text).and_call_original
expect(prawn).to receive(:width_of).with('some text', hash_including(:kerning, :size)).and_call_original
process_and_render
end
end
context 'when xml:space is unspecified' do
let(:attributes) { '' }
it 'strips space' do
allow(prawn).to receive(:width_of).and_call_original
allow(prawn).to receive(:draw_text).and_call_original
expect(prawn).to receive(:width_of).with('some text', hash_including(:kerning, :size)).and_call_original
process_and_render
end
end
end
describe 'conventional whitespace handling' do
let(:svg) do
<<~SVG
Some text here
More text
Even more
leading goodness
ok
SVG
end
it 'correctly apportions white space between the tags' do
drawn_texts = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
drawn_texts << text
method.call(text, **opts)
end
process_and_render
expect(drawn_texts).to eq ['Some text here ', 'More text', 'Even more', ' leading goodness ', 'ok']
end
end
describe 'when text-anchor is specified' do
let(:svg) { 'Text ' }
let(:element) { Prawn::SVG::Elements::Container.new(document, document.root, [], fake_state) }
it 'should inherit text-anchor from parent element' do
allow(prawn).to receive(:width_of).and_return(40.0)
expect(prawn).to receive(:translate).with(-20.0, 0).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(:at)).and_call_original
element.process
renderer.render_calls(prawn, element.calls)
end
end
describe 'letter-spacing' do
let(:svg) { 'spaced ' }
it 'calls character_spacing with the requested size' do
allow(prawn).to receive(:font).and_call_original
allow(prawn).to receive(:character_spacing).and_call_original
expect(prawn).to receive(:font).with('Helvetica', style: :normal).at_least(:once).and_call_original
expect(prawn).to receive(:character_spacing).with(5.0).at_least(:once).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(size: 16, at: anything)).and_call_original
process_and_render
end
end
describe 'word-spacing' do
let(:svg) { 'hello world ' }
it 'calls word_spacing with the requested size' do
allow(prawn).to receive(:word_spacing).and_call_original
expect(prawn).to receive(:word_spacing).with(10.0).at_least(:once).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(size: 16, at: anything)).and_call_original
process_and_render
end
context 'when set to normal' do
let(:svg) { 'hello world ' }
it 'uses default word spacing' do
allow(prawn).to receive(:word_spacing).and_call_original
expect(prawn).to receive(:word_spacing).with(0).at_least(:once).and_call_original
allow(prawn).to receive(:draw_text).and_call_original
process_and_render
end
end
context 'with negative value' do
let(:svg) { 'hello world test ' }
it 'accepts negative values' do
allow(prawn).to receive(:word_spacing).and_call_original
expect(prawn).to receive(:word_spacing).with(-3.0).at_least(:once).and_call_original
allow(prawn).to receive(:draw_text).and_call_original
process_and_render
end
end
context 'inherited by tspan' do
let(:svg) { 'hello world ' }
it 'inherits word-spacing to child elements' do
allow(prawn).to receive(:word_spacing).and_call_original
expect(prawn).to receive(:word_spacing).with(8.0).at_least(:once).and_call_original
allow(prawn).to receive(:draw_text).and_call_original
process_and_render
end
end
end
describe 'kerning' do
context 'when set to auto (default)' do
let(:svg) { 'kerned text ' }
it 'enables font kerning' do
allow(prawn).to receive(:width_of).and_call_original
expect(prawn).to receive(:width_of).with('kerned text', hash_including(kerning: true)).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(kerning: true)).and_call_original
process_and_render
end
end
context 'when set to 0' do
let(:svg) { 'no kerning ' }
it 'disables font kerning' do
allow(prawn).to receive(:width_of).and_call_original
expect(prawn).to receive(:width_of).with('no kerning', hash_including(kerning: false)).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(kerning: false)).and_call_original
process_and_render
end
end
context 'when set to a length value' do
let(:svg) { 'spaced ' }
it 'disables font kerning and applies kerning as character spacing' do
allow(prawn).to receive(:width_of).and_call_original
allow(prawn).to receive(:character_spacing).and_call_original
expect(prawn).to receive(:width_of).with('spaced', hash_including(kerning: false)).and_call_original
expect(prawn).to receive(:character_spacing).with(3.0).at_least(:once).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(kerning: false)).and_call_original
process_and_render
end
end
context 'when combined with letter-spacing' do
let(:svg) { 'combined ' }
it 'adds kerning value to letter-spacing' do
allow(prawn).to receive(:width_of).and_call_original
allow(prawn).to receive(:character_spacing).and_call_original
expect(prawn).to receive(:character_spacing).with(5.0).at_least(:once).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(kerning: false)).and_call_original
process_and_render
end
end
context 'inherited by tspan' do
let(:svg) { 'hello world ' }
it 'inherits kerning to child elements' do
allow(prawn).to receive(:width_of).and_call_original
allow(prawn).to receive(:character_spacing).and_call_original
expect(prawn).to receive(:character_spacing).with(4.0).at_least(:once).and_call_original
allow(prawn).to receive(:draw_text).and_call_original
process_and_render
end
end
context 'with font-kerning="none" (SVG 2)' do
let(:svg) { 'no kerning ' }
it 'disables font kerning' do
allow(prawn).to receive(:width_of).and_call_original
expect(prawn).to receive(:width_of).with('no kerning', hash_including(kerning: false)).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(kerning: false)).and_call_original
process_and_render
end
end
context 'with font-kerning="normal" (SVG 2)' do
let(:svg) { 'kerned ' }
it 'enables font kerning' do
allow(prawn).to receive(:width_of).and_call_original
expect(prawn).to receive(:width_of).with('kerned', hash_including(kerning: true)).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(kerning: true)).and_call_original
process_and_render
end
end
context 'when kerning property overrides font-kerning' do
let(:svg) { 'overridden ' }
it 'uses the kerning property value' do
allow(prawn).to receive(:width_of).and_call_original
allow(prawn).to receive(:character_spacing).and_call_original
expect(prawn).to receive(:character_spacing).with(2.0).at_least(:once).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(kerning: false)).and_call_original
process_and_render
end
end
end
describe 'text-decoration' do
describe 'underline' do
let(:svg) { 'underlined ' }
it 'draws an underline' do
allow(prawn).to receive(:width_of).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(:size, :at)).and_call_original
expect(prawn).to receive(:width_of).with('underlined', hash_including(:kerning, :size)).at_least(:once).and_call_original
expect(prawn).to receive(:fill_rectangle).with(
[0, be_within(1).of(598.56)], be_within(0.5).of(75), be_within(0.5).of(0.96)
).and_call_original
process_and_render
end
end
describe 'overline' do
let(:svg) { 'overlined ' }
it 'draws an overline above the text' do
expect(prawn).to receive(:fill_rectangle).with(
[0, be_within(2).of(616)], be_within(1).of(65), be_within(0.5).of(0.96)
).and_call_original
process_and_render
end
end
describe 'line-through' do
let(:svg) { 'struck ' }
it 'draws a line through the text' do
expect(prawn).to receive(:fill_rectangle).with(
[0, be_within(2).of(605)], be_within(0.5).of(43), be_within(0.5).of(0.96)
).and_call_original
process_and_render
end
end
describe 'multiple decorations' do
let(:svg) { 'decorated ' }
it 'draws both underline and line-through' do
expect(prawn).to receive(:fill_rectangle).twice.and_call_original
process_and_render
end
end
end
describe 'link' do
let(:fake_state) do
state = super()
state.anchor_href = 'http://example.com'
state
end
let(:svg) { 'a link ' }
it 'marks the element to be underlined' do
expect(prawn).to receive(:link_annotation).with(
[0.0, 596.688, 37.344, 615.184],
{
A: {
S: :URI,
Type: :Action,
URI: 'http://example.com'
},
Border: [0, 0, 0]
}
)
process_and_render
end
end
describe 'fill/stroke modes' do
context 'with a stroke and no fill' do
let(:svg) { 'stroked ' }
it 'calls text_rendering_mode with the requested options' do
element.process
calls_flat = flatten_calls(element.calls)
expect(calls_flat).to include(['stroke_color', ['ff0000'], {}])
allow(prawn).to receive(:font).and_call_original
allow(prawn).to receive(:text_rendering_mode).and_call_original
expect(prawn).to receive(:font).with('Helvetica', style: :normal).at_least(:once).and_call_original
expect(prawn).to receive(:text_rendering_mode).with(:stroke).at_least(:once).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(size: 16, at: anything)).and_call_original
element.render(prawn, renderer)
end
end
context 'with a mixture of everything' do
let(:svg) do
'stroked both neither '
end
it 'calls text_rendering_mode with the requested options' do
element.process
calls_flat = flatten_calls(element.calls)
expect(calls_flat).to include(['stroke_color', ['ff0000'], {}])
allow(prawn).to receive(:text_rendering_mode).and_call_original
expect(prawn).to receive(:text_rendering_mode).with(:stroke).at_least(:once).and_call_original
expect(prawn).to receive(:text_rendering_mode).with(:fill_stroke).at_least(:once).and_call_original
expect(prawn).to receive(:text_rendering_mode).with(:invisible).at_least(:once).and_call_original
allow(prawn).to receive(:draw_text).and_call_original
expect(prawn).to receive(:save_graphics_state).at_least(:once).and_call_original
expect(prawn).to receive(:restore_graphics_state).at_least(:once).and_call_original
element.render(prawn, renderer)
end
end
end
describe 'font finding' do
context 'with a font that exists' do
let(:svg) { 'hello ' }
it 'finds the font and uses it' do
allow(prawn).to receive(:font).and_call_original
expect(prawn).to receive(:font).with('Courier', style: :normal).at_least(:once).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(size: 16, at: anything)).and_call_original
process_and_render
end
end
context "with a font that doesn't exist" do
let(:svg) { 'hello ' }
it 'uses the fallback font' do
allow(prawn).to receive(:font).and_call_original
expect(prawn).to receive(:font).with('Times-Roman', style: :normal).at_least(:once).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(size: 16, at: anything)).and_call_original
process_and_render
end
context 'when there is no fallback font' do
before { document.font_registry.installed_fonts.delete('Times-Roman') }
it "doesn't call the font method and logs a warning" do
process_and_render
expect(document.warnings.first).to include 'is not a known font'
end
end
end
end
describe 'fallback fonts' do
before do
ttf = File.expand_path('../../../sample_ttf/OpenSans-SemiboldItalic.ttf', __dir__)
prawn.font_families.update('Open Sans' => { normal: ttf })
end
let(:document) do
Prawn::SVG::Document.new(svg, [800, 600], { enable_web_requests: false }, font_registry: Prawn::SVG::FontRegistry.new(prawn.font_families))
end
context 'when text contains characters unsupported by the primary built-in font' do
let(:svg) { "Hello \u0167 " }
it 'uses the fallback font for unsupported characters' do
drawn = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
drawn << [text, prawn.font.family]
method.call(text, **opts)
end
process_and_render
expect(drawn).to include(['Hello ', 'Helvetica'])
expect(drawn).to include(["\u0167", 'Open Sans'])
end
end
end
describe '' do
let(:svg) { 'my reference text ' }
let(:element) { Prawn::SVG::Elements::Root.new(document, document.root, [], fake_state) }
it 'references the text' do
element.process
expect(element.calls.any? { |call| call[0] == 'svg:render' }).to be true
end
end
describe 'dx and dy attributes' do
let(:svg) { 'Hi there, this is a good test ' }
it 'correctly calculates the positions of the text' do
expect(prawn).to receive(:draw_text).with(anything, hash_including(at: [40.0, anything])).and_call_original # 10 + 30
expect(prawn).to receive(:draw_text).with(anything, hash_including(at: [70.0, anything])).and_call_original # 20 + 50
allow(prawn).to receive(:draw_text).and_call_original
process_and_render
end
end
describe 'rotate attribute' do
let(:svg) { 'Hi this ok! ' }
it 'correctly processes rotated text' do
drawn = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
drawn << [text, opts[:rotate]]
method.call(text, **opts)
end
process_and_render
expect(drawn).to include(['H', -10.0])
expect(drawn).to include(['i', -20.0])
expect(drawn).to include([' ', -30.0])
expect(drawn).to include(['o', -90.0])
expect(drawn).to include(['k', -100.0])
this_entry = drawn.find { |text, _| text == 'this' }
expect(this_entry[1]).to be_nil
end
end
describe "when there's a comment inside the text element" do
let(:svg) { 'Hi there ' }
it 'ignores the comment' do
expect(renderer).to receive(:render_calls).with(prawn, anything).and_call_original
expect(prawn).to receive(:width_of).at_least(:once).and_call_original
process_and_render
end
end
describe 'baseline-shift' do
let(:svg) do
<<~SVG
Normalsuper after
SVG
end
it 'shifts superscript text upward' do
positions = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
positions << [text, opts[:at]]
method.call(text, **opts)
end
process_and_render
normal_y = positions.find { |t, _| t == 'Normal' }[1][1]
super_y = positions.find { |t, _| t == 'super' }[1][1]
after_y = positions.find { |t, _| t == 'after' }[1][1]
expect(super_y).to be > normal_y
expect(after_y).to eq normal_y
end
context 'with sub' do
let(:svg) do
<<~SVG
Normalsub after
SVG
end
it 'shifts subscript text downward' do
positions = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
positions << [text, opts[:at]]
method.call(text, **opts)
end
process_and_render
normal_y = positions.find { |t, _| t == 'Normal' }[1][1]
sub_y = positions.find { |t, _| t == 'sub' }[1][1]
after_y = positions.find { |t, _| t == 'after' }[1][1]
expect(sub_y).to be < normal_y
expect(after_y).to eq normal_y
end
end
context 'with a percentage value' do
let(:svg) do
<<~SVG
Normalshifted after
SVG
end
it 'shifts text by the percentage of font size' do
positions = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
positions << [text, opts[:at]]
method.call(text, **opts)
end
process_and_render
normal_y = positions.find { |t, _| t == 'Normal' }[1][1]
shifted_y = positions.find { |t, _| t == 'shifted' }[1][1]
expect(shifted_y - normal_y).to be_within(0.01).of(10.0)
end
end
context 'with a negative length value' do
let(:svg) do
<<~SVG
Normalshifted after
SVG
end
it 'shifts text down by the specified amount' do
positions = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
positions << [text, opts[:at]]
method.call(text, **opts)
end
process_and_render
normal_y = positions.find { |t, _| t == 'Normal' }[1][1]
shifted_y = positions.find { |t, _| t == 'shifted' }[1][1]
expect(shifted_y - normal_y).to be_within(0.01).of(-5.0)
end
end
context 'with nested baseline-shifts' do
let(:svg) do
<<~SVG
Normaldouble after
SVG
end
it 'accumulates shifts' do
positions = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
positions << [text, opts[:at]]
method.call(text, **opts)
end
process_and_render
normal_y = positions.find { |t, _| t == 'Normal' }[1][1]
double_y = positions.find { |t, _| t == 'double' }[1][1]
# Both tspans inherit 20px font-size; shift = ascender_ratio * 20 / 1.2 each
single_shift = (prawn.font.ascender / prawn.font_size) * 20 / 1.2
expect(double_y - normal_y).to be_within(0.1).of(single_shift * 2)
end
end
end
describe 'alignment-baseline' do
context 'with middle value on a tspan' do
let(:svg) { 'Normalmid after ' }
it 'shifts text when alignment-baseline is set' do
positions = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
positions << [text, opts[:at]]
method.call(text, **opts)
end
process_and_render
normal_y = positions.find { |t, _| t == 'Normal' }[1][1]
mid_y = positions.find { |t, _| t == 'mid' }[1][1]
after_y = positions.find { |t, _| t == 'after' }[1][1]
expect(mid_y).to be < normal_y
expect(after_y).to eq normal_y
end
end
context 'with auto value' do
let(:svg) { 'Normalsame ' }
it 'does not shift text' do
positions = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
positions << [text, opts[:at]]
method.call(text, **opts)
end
process_and_render
normal_y = positions.find { |t, _| t == 'Normal' }[1][1]
same_y = positions.find { |t, _| t == 'same' }[1][1]
expect(same_y).to eq normal_y
end
end
context 'when overriding dominant-baseline' do
let(:svg) { 'Hangingalpha ' }
it 'uses alignment-baseline instead of inherited dominant-baseline' do
positions = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
positions << [text, opts[:at]]
method.call(text, **opts)
end
process_and_render
hanging_y = positions.find { |t, _| t == 'Hanging' }[1][1]
alpha_y = positions.find { |t, _| t == 'alpha' }[1][1]
expect(alpha_y).to be > hanging_y
end
end
context 'with hanging value' do
let(:svg) { 'Normalhang ' }
it 'shifts text entirely below the baseline' do
positions = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
positions << [text, opts[:at]]
method.call(text, **opts)
end
process_and_render
normal_y = positions.find { |t, _| t == 'Normal' }[1][1]
hang_y = positions.find { |t, _| t == 'hang' }[1][1]
# hanging shifts down by the full ascender (same as text-before-edge)
expect(hang_y).to be < normal_y
ascender_shift = (prawn.font.ascender / prawn.font_size) * 20
expect(normal_y - hang_y).to be_within(0.01).of(ascender_shift)
end
end
context 'with mathematical value' do
let(:svg) { 'Normalmath ' }
it 'shifts text down by half the em box height' do
positions = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
positions << [text, opts[:at]]
method.call(text, **opts)
end
process_and_render
normal_y = positions.find { |t, _| t == 'Normal' }[1][1]
math_y = positions.find { |t, _| t == 'math' }[1][1]
# mathematical = (ascender + descender) / 2
expected_shift = ((prawn.font.ascender + prawn.font.descender) / prawn.font_size) * 20 / 2.0
expect(normal_y - math_y).to be_within(0.01).of(expected_shift)
end
end
context 'with ideographic value' do
let(:svg) { 'Normalideo ' }
it 'shifts text up so all glyphs are above the baseline' do
positions = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
positions << [text, opts[:at]]
method.call(text, **opts)
end
process_and_render
normal_y = positions.find { |t, _| t == 'Normal' }[1][1]
ideo_y = positions.find { |t, _| t == 'ideo' }[1][1]
# ideographic shifts up by descender amount
expect(ideo_y).to be > normal_y
end
end
context 'when not inherited by child elements' do
let(:svg) { 'parentchild ' }
it 'does not apply to nested children' do
positions = []
allow(prawn).to receive(:draw_text).and_wrap_original do |method, text, **opts|
positions << [text, opts[:at]]
method.call(text, **opts)
end
process_and_render
parent_y = positions.find { |t, _| t == 'parent' }[1][1]
child_y = positions.find { |t, _| t == 'child' }[1][1]
expect(child_y).to_not eq parent_y
end
end
end
describe 'writing-mode' do
context 'with vertical-rl' do
let(:svg) { 'Vertical ' }
it 'rotates the text 90 degrees clockwise around the text origin' do
expect(prawn).to receive(:rotate).with(-90, origin: [50.0, 500.0]).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(:at)).and_call_original
process_and_render
end
end
context 'with vertical-lr' do
let(:svg) { 'Vertical ' }
it 'rotates the text 90 degrees clockwise' do
expect(prawn).to receive(:rotate).with(-90, origin: [50.0, 500.0]).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(:at)).and_call_original
process_and_render
end
end
context 'with horizontal-tb (default)' do
let(:svg) { 'Horizontal ' }
it 'does not rotate the text' do
expect(prawn).not_to receive(:rotate)
expect(prawn).to receive(:draw_text).with(anything, hash_including(:at)).and_call_original
process_and_render
end
end
context 'inherited from parent' do
let(:svg) { 'Inherited ' }
let(:element) { Prawn::SVG::Elements::Container.new(document, document.root, [], fake_state) }
it 'inherits writing-mode and rotates the text' do
element.process
expect(prawn).to receive(:rotate).with(-90, origin: [50.0, 500.0]).and_call_original
expect(prawn).to receive(:draw_text).with(anything, hash_including(:at)).and_call_original
renderer.render_calls(prawn, element.calls)
end
end
end
describe 'when a use element references a tspan element' do
let(:svg) do
<<~SVG
Referenced text
SVG
end
let(:element) { Prawn::SVG::Elements::Root.new(document, document.root, [], fake_state) }
it 'emits a warning that tspan cannot be used' do
element.process
expect(document.warnings).to include('attempted to a component inside a text element, this is not supported')
end
end
describe 'when a use element references a text element' do
let(:svg) do
<<~SVG
Referenced text
SVG
end
let(:element) { Prawn::SVG::Elements::Root.new(document, document.root, [], fake_state) }
it 'processes the referenced text element' do
element.process
expect(document.warnings).to eq []
expect(flatten_calls(element.base_calls).map(&:first)).to include 'svg:render'
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/elements/use_spec.rb 0000664 0000000 0000000 00000020765 15155455534 0024240 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Elements::Use do
let(:document) { Prawn::SVG::Document.new(svg, [800, 600], options) }
let(:element) { Prawn::SVG::Elements::Root.new(document, document.root, []) }
let(:options) { { enable_web_requests: false } }
let(:flattened_calls) { flatten_calls(element.base_calls) }
describe 'local references' do
let(:svg) do
<<~SVG
SVG
end
before { element.process }
it 'renders the referenced element' do
expect(flattened_calls).to include ['fill_color', ['ff0000'], {}]
end
it 'applies the x/y translation' do
expect(flattened_calls).to include ['translate', [10.0, -10.0], {}]
end
end
describe 'local reference to symbol' do
let(:svg) do
<<~SVG
SVG
end
before { element.process }
it 'renders the symbol as a viewport' do
expect(flattened_calls).to include ['fill_color', ['0000ff'], {}]
end
end
describe 'missing local reference' do
let(:svg) do
<<~SVG
SVG
end
before { element.process }
it 'emits a warning' do
expect(document.warnings.any? { |w| w.include?('nonexistent') }).to be true
end
end
describe 'missing href' do
let(:svg) do
<<~SVG
SVG
end
before { element.process }
it 'emits a warning about missing href' do
expect(document.warnings).to include 'use tag must have an href or xlink:href'
end
end
describe 'external references' do
let(:external_svg) do
<<~SVG
SVG
end
describe 'referencing an element from an external SVG' do
let(:svg) do
<<~SVG
SVG
end
before do
document.url_loader.add_to_cache('http://use-test.invalid/icons.svg', external_svg)
element.process
end
it 'renders the external element' do
expect(document.warnings).to be_empty
expect(flattened_calls.any? { |c| c[0] == 'rectangle' }).to be true
end
it 'applies CSS styles from the external document' do
expect(flattened_calls).to include ['fill_color', ['008000'], {}]
end
end
describe 'referencing a symbol from an external SVG' do
let(:svg) do
<<~SVG
SVG
end
before do
document.url_loader.add_to_cache('http://use-test.invalid/icons.svg', external_svg)
element.process
end
it 'renders the external symbol as a viewport' do
expect(document.warnings).to be_empty
expect(flattened_calls.any? { |c| c[0] == 'circle' }).to be true
end
end
describe 'referencing a group from an external SVG' do
let(:svg) do
<<~SVG
SVG
end
before do
document.url_loader.add_to_cache('http://use-test.invalid/icons.svg', external_svg)
element.process
end
it 'renders all children of the external group' do
expect(document.warnings).to be_empty
expect(flattened_calls.any? { |c| c[0] == 'rectangle' }).to be true
expect(flattened_calls.any? { |c| c[0] == 'circle' }).to be true
end
it 'applies the x/y translation' do
expect(flattened_calls).to include ['translate', [20.0, -20.0], {}]
end
end
describe 'external href without fragment identifier' do
let(:svg) do
<<~SVG
SVG
end
before do
document.url_loader.add_to_cache('http://use-test.invalid/icons.svg', external_svg)
element.process
end
it 'emits a warning about missing fragment' do
expect(document.warnings.any? { |w| w.include?('fragment') }).to be true
end
end
describe 'external href with nonexistent element ID' do
let(:svg) do
<<~SVG
SVG
end
before do
document.url_loader.add_to_cache('http://use-test.invalid/icons.svg', external_svg)
element.process
end
it 'emits a warning' do
expect(document.warnings.any? { |w| w.include?('nonexistent') }).to be true
end
end
describe 'external href with unreachable URL' do
let(:svg) do
<<~SVG
SVG
end
before { element.process }
it 'emits a warning about load failure' do
expect(document.warnings.any? { |w| w.include?('could not load') }).to be true
end
end
describe 'caching external SVG documents' do
let(:svg) do
<<~SVG
SVG
end
before do
document.url_loader.add_to_cache('http://use-test.invalid/icons.svg', external_svg)
element.process
end
it 'caches the parsed external document' do
expect(document.external_svg_cache).to have_key('http://use-test.invalid/icons.svg')
end
it 'renders both elements without errors' do
expect(document.warnings).to be_empty
expect(flattened_calls.select { |c| c[0] == 'rectangle' }.length).to be >= 2
end
end
describe 'using href attribute instead of xlink:href' do
let(:svg) do
<<~SVG
SVG
end
before do
document.url_loader.add_to_cache('http://use-test.invalid/icons.svg', external_svg)
element.process
end
it 'renders the external element' do
expect(document.warnings).to be_empty
expect(flattened_calls.any? { |c| c[0] == 'rectangle' }).to be true
end
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/font_metrics_spec.rb 0000664 0000000 0000000 00000000624 15155455534 0024314 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::FontMetrics do
let(:pdf) { Prawn::Document.new }
describe '.underline_metrics' do
it 'does not return the same values for different font sizes' do
underline_10 = described_class.underline_metrics(pdf, 10)
underline_20 = described_class.underline_metrics(pdf, 20)
expect(underline_10).to_not eq(underline_20)
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/font_registry_spec.rb 0000664 0000000 0000000 00000037720 15155455534 0024525 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::FontRegistry do
describe '#load' do
let(:pdf) { Prawn::Document.new }
let(:font_registry) { Prawn::SVG::FontRegistry.new(pdf.font_families) }
it 'matches a built in font' do
expect(font_registry.load("blah, 'courier', nothing").name).to eq('Courier')
end
it 'matches a default font' do
expect(font_registry.load('serif').name).to be_truthy
expect(font_registry.load('blah, serif').name).to be_truthy
expect(font_registry.load('blah, serif , test').name).to eq('Times-Roman')
end
it 'allows generic font family to be remapped in font registry' do
pdf = Prawn::Document.new
pdf.font_families.update('serif' => { normal: 'Courier' })
registry = Prawn::SVG::FontRegistry.new(pdf.font_families)
font = registry.load('serif')
expect(font.name).to eq('serif')
end
if Prawn::SVG::FontRegistry.new({}).installed_fonts['Verdana']
it 'matches a font installed on the system' do
expect(font_registry.load('verdana, sans-serif').name).to eq('Verdana')
expect(font_registry.load('VERDANA, sans-serif').name).to eq('Verdana')
expect(font_registry.load('something, "Times New Roman", serif').name).to be_truthy
expect(font_registry.load('something, Times New Roman, serif').name).to eq('Times New Roman')
end
else
it "not running font test because we couldn't find Verdana installed on the system"
end
it "returns nil if it can't find any such font" do
expect(font_registry.load('blah, thing')).to be_nil
expect(font_registry.load('')).to be_nil
end
it 'handles CSS font weights' do
font = font_registry.load('courier', '700')
expect(font.weight).to eq(:bold)
end
it 'normalizes multiple spaces in font names' do
font = font_registry.load('courier , times')
expect(font.name).to eq('Courier')
end
it 'falls back when requested weight is unavailable' do
font = font_registry.load('courier', :black)
expect(font.name).to eq('Courier')
end
it 'handles font weight and style parameters' do
font = font_registry.load('courier', :bold, :italic)
expect(font.weight).to eq(:bold)
expect(font.style).to eq(:italic)
end
it 'converts CSS numeric weights to symbols' do
expect(font_registry.load('courier', '100').weight).to eq(:normal) # thin falls back to normal for Courier
expect(font_registry.load('courier', '400').weight).to eq(:normal)
expect(font_registry.load('courier', '700').weight).to eq(:bold)
end
it 'falls back from style when unavailable' do
font = font_registry.load('courier', :normal, :italic)
expect(font.name).to eq('Courier')
end
it 'processes comma-separated font families' do
font = font_registry.load('nonexistent, courier, times')
expect(font.name).to eq('Courier')
end
it 'handles quoted font names with commas' do
font = font_registry.load('"font, with comma", courier')
expect(font.name).to eq('Courier')
end
describe 'font-stretch matching' do
before do
allow(font_registry).to receive(:installed_fonts).and_return({
'TestFont' => {
normal: 'test.ttf',
bold: 'test-bold.ttf',
italic: 'test-italic.ttf',
condensed: 'test-condensed.ttf',
condensed_bold: 'test-condensed-bold.ttf',
condensed_italic: 'test-condensed-italic.ttf',
expanded: 'test-expanded.ttf'
}
})
allow(font_registry).to receive(:correctly_cased_font_name).and_return('TestFont')
end
it 'selects a condensed font when font-stretch is condensed' do
font = font_registry.load('TestFont', :normal, nil, 'condensed')
expect(font.subfamily).to eq(:condensed)
end
it 'selects a condensed bold font' do
font = font_registry.load('TestFont', :bold, nil, 'condensed')
expect(font.subfamily).to eq(:condensed_bold)
end
it 'selects a condensed italic font' do
font = font_registry.load('TestFont', :normal, :italic, 'condensed')
expect(font.subfamily).to eq(:condensed_italic)
end
it 'selects an expanded font' do
font = font_registry.load('TestFont', :normal, nil, 'expanded')
expect(font.subfamily).to eq(:expanded)
end
it 'falls back to nearest narrower stretch when exact stretch is not available' do
font = font_registry.load('TestFont', :normal, nil, 'semi-condensed')
expect(font.subfamily).to eq(:condensed)
end
it 'falls back to normal when no stretch variant is available' do
allow(font_registry).to receive(:installed_fonts).and_return({
'TestFont' => { normal: 'test.ttf', bold: 'test-bold.ttf' }
})
font = font_registry.load('TestFont', :normal, nil, 'condensed')
expect(font.subfamily).to eq(:normal)
end
it 'ignores stretch when set to normal' do
font = font_registry.load('TestFont', :bold, nil, 'normal')
expect(font.subfamily).to eq(:bold)
end
it 'uses weight fallback within stretch' do
font = font_registry.load('TestFont', :extrabold, nil, 'condensed')
expect(font.subfamily).to eq(:condensed_bold)
end
it 'falls back through weight within stretch before dropping style' do
font = font_registry.load('TestFont', :bold, :italic, 'condensed')
expect(font.subfamily).to eq(:condensed_italic)
end
it 'picks closest heavier weight in stretch when exact weight is unavailable' do
allow(font_registry).to receive(:installed_fonts).and_return({
'TestFont' => {
normal: 'test.ttf',
condensed_bold: 'test-condensed-bold.ttf'
}
})
font = font_registry.load('TestFont', :normal, nil, 'condensed')
expect(font.subfamily).to eq(:condensed_bold)
end
end
describe 'weight fallbacks' do
before do
# Mock a font family with only normal and bold available
allow(font_registry).to receive(:installed_fonts).and_return({
'TestFont' => { normal: 'test.ttf', bold: 'test-bold.ttf' }
})
allow(font_registry).to receive(:correctly_cased_font_name).and_return('TestFont')
end
it 'falls back from thin through to normal' do
font = font_registry.load('TestFont', :thin)
expect(font.weight).to eq(:normal)
end
it 'falls back from light to normal' do
font = font_registry.load('TestFont', :light)
expect(font.weight).to eq(:normal)
end
it 'falls back from medium to normal' do
font = font_registry.load('TestFont', :medium)
expect(font.weight).to eq(:normal)
end
it 'falls back from semibold to bold to normal' do
font = font_registry.load('TestFont', :semibold)
expect(font.weight).to eq(:bold)
end
it 'falls back from extrabold to bold' do
font = font_registry.load('TestFont', :extrabold)
expect(font.weight).to eq(:bold)
end
it 'falls back from black to bold' do
font = font_registry.load('TestFont', :black)
expect(font.weight).to eq(:bold)
end
context 'when only normal weight is available' do
before do
allow(font_registry).to receive(:installed_fonts).and_return({
'TestFont' => { normal: 'test.ttf' }
})
end
it 'falls back from bold to normal' do
font = font_registry.load('TestFont', :bold)
expect(font.weight).to eq(:normal)
end
it 'falls back through the entire chain to normal' do
font = font_registry.load('TestFont', :black)
expect(font.weight).to eq(:normal)
end
end
context 'when all weights are available' do
before do
allow(font_registry).to receive(:installed_fonts).and_return({
'TestFont' => {
thin: 'test-thin.ttf',
extralight: 'test-extralight.ttf',
light: 'test-light.ttf',
normal: 'test.ttf',
medium: 'test-medium.ttf',
semibold: 'test-semibold.ttf',
bold: 'test-bold.ttf',
extrabold: 'test-extrabold.ttf',
black: 'test-black.ttf'
}
})
end
it 'returns exact weight matches without fallback' do
expect(font_registry.load('TestFont', :thin).weight).to eq(:thin)
expect(font_registry.load('TestFont', :extralight).weight).to eq(:extralight)
expect(font_registry.load('TestFont', :light).weight).to eq(:light)
expect(font_registry.load('TestFont', :medium).weight).to eq(:medium)
expect(font_registry.load('TestFont', :semibold).weight).to eq(:semibold)
expect(font_registry.load('TestFont', :extrabold).weight).to eq(:extrabold)
expect(font_registry.load('TestFont', :black).weight).to eq(:black)
end
end
context 'weight aliases' do
it 'matches ultralight when looking for extralight' do
allow(font_registry).to receive(:installed_fonts).and_return({
'TestFont' => { ultralight: 'test-ul.ttf', normal: 'test.ttf' }
})
font = font_registry.load('TestFont', :extralight)
expect(font.subfamily).to eq(:ultralight)
end
it 'matches heavy when looking for black' do
allow(font_registry).to receive(:installed_fonts).and_return({
'TestFont' => { normal: 'test.ttf', heavy: 'test-heavy.ttf' }
})
font = font_registry.load('TestFont', :black)
expect(font.subfamily).to eq(:heavy)
end
it 'matches demi_bold when looking for semibold' do
allow(font_registry).to receive(:installed_fonts).and_return({
'TestFont' => { normal: 'test.ttf', demi_bold: 'test-db.ttf' }
})
font = font_registry.load('TestFont', :semibold)
expect(font.subfamily).to eq(:demi_bold)
end
end
end
end
describe '#installed_fonts' do
let(:ttc) { instance_double(Prawn::SVG::TTC, fonts: []) }
let(:ttf) { instance_double(Prawn::SVG::TTF, family: 'Awesome Font', subfamily: 'Italic', weight_class: 400) }
let(:ttf2) { instance_double(Prawn::SVG::TTF, family: 'Awesome Font', subfamily: 'Regular', weight_class: 400) }
before { Prawn::SVG::FontRegistry.external_font_families.clear }
let(:pdf) do
doc = Prawn::Document.new
doc.font_families.update({
'Awesome Font' => { italic: 'second.ttf', normal: 'file.ttf' }
})
doc
end
let(:font_registry) { Prawn::SVG::FontRegistry.new(pdf.font_families) }
it 'does not override existing entries in pdf when loading external fonts' do
expect(Prawn::SVG::FontRegistry).to receive(:font_path).and_return(['x'])
expect(Dir).to receive(:[]).with('x/**/*').and_return(['file.ttf', 'second.ttf'])
expect(Prawn::SVG::TTC).to receive(:new).twice.and_return(ttc)
expect(Prawn::SVG::TTF).to receive(:new).with('file.ttf').and_return(ttf)
expect(Prawn::SVG::TTF).to receive(:new).with('second.ttf').and_return(ttf2)
expect(File).to receive(:file?).at_least(:once).and_return(true)
Prawn::SVG::FontRegistry.load_external_fonts
font_registry.installed_fonts
existing_font = font_registry.installed_fonts['Awesome Font']
expect(existing_font).to eq(italic: 'second.ttf', normal: 'file.ttf')
end
end
describe '::load_external_fonts' do
before { Prawn::SVG::FontRegistry.external_font_families.clear }
context 'with TTF files' do
let(:ttc) { instance_double(Prawn::SVG::TTC, fonts: []) }
let(:ttf) { instance_double(Prawn::SVG::TTF, family: 'Awesome Font', subfamily: 'Italic', weight_class: 400) }
let(:ttf2) { instance_double(Prawn::SVG::TTF, family: 'Awesome Font', subfamily: 'Regular', weight_class: 400) }
it 'scans the font path and loads in some fonts' do
expect(Prawn::SVG::FontRegistry).to receive(:font_path).and_return(['x'])
expect(Dir).to receive(:[]).with('x/**/*').and_return(['file.ttf', 'second.ttf'])
expect(Prawn::SVG::TTC).to receive(:new).twice.and_return(ttc)
expect(Prawn::SVG::TTF).to receive(:new).with('file.ttf').and_return(ttf)
expect(Prawn::SVG::TTF).to receive(:new).with('second.ttf').and_return(ttf2)
expect(File).to receive(:file?).at_least(:once).and_return(true)
Prawn::SVG::FontRegistry.load_external_fonts
result = Prawn::SVG::FontRegistry.external_font_families
expect(result).to eq('Awesome Font' => { italic: 'file.ttf', normal: 'second.ttf' })
end
end
context 'with TTC files' do
let(:ttc) do
instance_double(Prawn::SVG::TTC, fonts: [
{ family: 'Collection Font', subfamily: 'Regular', index: 0 },
{ family: 'Collection Font', subfamily: 'Bold', index: 1 }
])
end
it 'stores fonts with file and index hash' do
expect(Prawn::SVG::FontRegistry).to receive(:font_path).and_return(['x'])
expect(Dir).to receive(:[]).with('x/**/*').and_return(['collection.ttc'])
expect(Prawn::SVG::TTC).to receive(:new).with('collection.ttc').and_return(ttc)
expect(File).to receive(:file?).at_least(:once).and_return(true)
Prawn::SVG::FontRegistry.load_external_fonts
result = Prawn::SVG::FontRegistry.external_font_families
expect(result).to eq('Collection Font' => {
normal: { file: 'collection.ttc', font: 0 },
bold: { file: 'collection.ttc', font: 1 }
})
end
end
context 'with a TTC containing multiple font families' do
let(:ttc) do
instance_double(Prawn::SVG::TTC, fonts: [
{ family: 'Font A', subfamily: 'Regular', index: 0 },
{ family: 'Font B', subfamily: 'Regular', index: 1 }
])
end
it 'registers each family separately' do
expect(Prawn::SVG::FontRegistry).to receive(:font_path).and_return(['x'])
expect(Dir).to receive(:[]).with('x/**/*').and_return(['multi.ttc'])
expect(Prawn::SVG::TTC).to receive(:new).with('multi.ttc').and_return(ttc)
expect(File).to receive(:file?).at_least(:once).and_return(true)
Prawn::SVG::FontRegistry.load_external_fonts
result = Prawn::SVG::FontRegistry.external_font_families
expect(result).to eq(
'Font A' => { normal: { file: 'multi.ttc', font: 0 } },
'Font B' => { normal: { file: 'multi.ttc', font: 1 } }
)
end
end
context 'when TTC fonts would override existing entries' do
let(:ttc) do
instance_double(Prawn::SVG::TTC, fonts: [
{ family: 'Existing Font', subfamily: 'Regular', index: 0 },
{ family: 'Existing Font', subfamily: 'Bold', index: 1 }
])
end
let(:ttc2) { instance_double(Prawn::SVG::TTC, fonts: []) }
let(:ttf) { instance_double(Prawn::SVG::TTF, family: 'Existing Font', subfamily: 'Regular', weight_class: 400) }
it 'does not override the first entry with a later one' do
expect(Prawn::SVG::FontRegistry).to receive(:font_path).and_return(['x'])
expect(Dir).to receive(:[]).with('x/**/*').and_return(['first.ttc', 'second.ttf'])
expect(Prawn::SVG::TTC).to receive(:new).with('first.ttc').and_return(ttc)
expect(Prawn::SVG::TTC).to receive(:new).with('second.ttf').and_return(ttc2)
expect(Prawn::SVG::TTF).to receive(:new).with('second.ttf').and_return(ttf)
expect(File).to receive(:file?).at_least(:once).and_return(true)
Prawn::SVG::FontRegistry.load_external_fonts
result = Prawn::SVG::FontRegistry.external_font_families
expect(result['Existing Font'][:normal]).to eq(file: 'first.ttc', font: 0)
end
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/font_spec.rb 0000664 0000000 0000000 00000004751 15155455534 0022573 0 ustar 00root root 0000000 0000000 require "#{File.dirname(__FILE__)}/../../spec_helper"
describe Prawn::SVG::Font do
describe '#initialize' do
it 'sets name, weight, and style' do
font = Prawn::SVG::Font.new('Arial', :bold, :italic)
expect(font.name).to eq('Arial')
expect(font.weight).to eq(:bold)
expect(font.style).to eq(:italic)
end
it 'sets stretch when provided' do
font = Prawn::SVG::Font.new('Arial', :bold, :italic, :condensed)
expect(font.stretch).to eq(:condensed)
end
it 'defaults stretch to nil' do
font = Prawn::SVG::Font.new('Arial', :bold, :italic)
expect(font.stretch).to be_nil
end
end
describe '#subfamily' do
it 'returns :normal when weight and style are both normal' do
font = Prawn::SVG::Font.new('Arial', :normal, nil)
expect(font.subfamily).to eq(:normal)
end
it 'returns style when weight is normal and style is present' do
font = Prawn::SVG::Font.new('Arial', :normal, :italic)
expect(font.subfamily).to eq(:italic)
end
it 'returns weight when only weight is present' do
font = Prawn::SVG::Font.new('Arial', :bold, nil)
expect(font.subfamily).to eq(:bold)
end
it 'returns combined weight and style when both are present' do
font = Prawn::SVG::Font.new('Arial', :bold, :italic)
expect(font.subfamily).to eq(:bold_italic)
end
it 'returns :normal when both weight and style are nil' do
font = Prawn::SVG::Font.new('Arial', nil, nil)
expect(font.subfamily).to eq(:normal)
end
context 'with stretch' do
it 'returns stretch alone when weight is normal and no style' do
font = Prawn::SVG::Font.new('Arial', :normal, nil, :condensed)
expect(font.subfamily).to eq(:condensed)
end
it 'returns stretch with style when weight is normal' do
font = Prawn::SVG::Font.new('Arial', :normal, :italic, :condensed)
expect(font.subfamily).to eq(:condensed_italic)
end
it 'returns stretch with weight' do
font = Prawn::SVG::Font.new('Arial', :bold, nil, :condensed)
expect(font.subfamily).to eq(:condensed_bold)
end
it 'returns stretch with weight and style' do
font = Prawn::SVG::Font.new('Arial', :bold, :italic, :condensed)
expect(font.subfamily).to eq(:condensed_bold_italic)
end
it 'ignores :normal stretch' do
font = Prawn::SVG::Font.new('Arial', :bold, nil, :normal)
expect(font.subfamily).to eq(:bold)
end
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/funciri_spec.rb 0000664 0000000 0000000 00000004022 15155455534 0023253 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::FuncIRI do
describe '.parse' do
it 'parses a URL' do
expect(described_class.parse('url(#foo)')).to eq(described_class.new('#foo'))
end
it 'parses a URL with whitespace' do
expect(described_class.parse('url( #foo )')).to eq(described_class.new('#foo'))
end
it 'parses a URL with quotes' do
expect(described_class.parse('url("#foo")')).to eq(described_class.new('#foo'))
end
it 'parses a URL with quotes and whitespace' do
expect(described_class.parse('url( "#foo" )')).to eq(described_class.new('#foo'))
end
it 'parses a URL with double quotes' do
expect(described_class.parse('url("#foo")')).to eq(described_class.new('#foo'))
end
it 'parses a URL with double quotes and whitespace' do
expect(described_class.parse('url( "#foo" )')).to eq(described_class.new('#foo'))
end
it 'parses a URL with single quotes' do
expect(described_class.parse("url('#foo')")).to eq(described_class.new('#foo'))
end
it 'parses a URL with single quotes and whitespace' do
expect(described_class.parse("url( '#foo' )")).to eq(described_class.new('#foo'))
end
it 'parses a URL with both single and double quotes' do
expect(described_class.parse('url("#foo")')).to eq(described_class.new('#foo'))
end
it 'parses a URL with both single and double quotes and whitespace' do
expect(described_class.parse('url( "#foo" )')).to eq(described_class.new('#foo'))
end
it 'parses a URL with both single and double quotes and whitespace' do
expect(described_class.parse('url( "#foo" )')).to eq(described_class.new('#foo'))
end
it 'parses a URL with escaped quotes' do
expect(described_class.parse('url("\\#foo")')).to eq(described_class.new('#foo'))
end
it 'ignores a non-URL value' do
expect(described_class.parse('foo')).to be_nil
expect(described_class.parse('foo(1)')).to be_nil
expect(described_class.parse('url(1, 2)')).to be_nil
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/interface_spec.rb 0000664 0000000 0000000 00000013776 15155455534 0023574 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Interface do
let(:bounds) { double(width: 800, height: 600, absolute_left: 0, absolute_top: 0) }
let(:prawn) { instance_double(Prawn::Document, font_families: {}, bounds: bounds, cursor: 600) }
let(:svg) { ' ' }
describe '#initialize' do
describe 'invalid option detection' do
it 'rejects invalid options when debug is on' do
allow(Prawn).to receive(:debug).and_return(true)
expect do
Prawn::SVG::Interface.new(svg, prawn, invalid: 'option')
end.to raise_error(Prawn::Errors::UnknownOption)
end
it 'does nothing if an invalid option is given and debug is off' do
Prawn::SVG::Interface.new(svg, prawn, invalid: 'option', enable_web_requests: false)
end
end
end
describe '#draw' do
context 'when the sizing object indicates the sizes are invalid' do
let(:interface) { Prawn::SVG::Interface.new(' ', prawn, { enable_web_requests: false }) }
it "doesn't draw anything and adds a warning" do
interface.draw
expect(interface.document.warnings).to eq ['Zero or negative sizing data means this SVG cannot be rendered']
end
end
context 'when log_warnings is true' do
let(:interface) { Prawn::SVG::Interface.new(' ', prawn, log_warnings: true, enable_web_requests: false) }
it 'outputs warnings to stderr via warn' do
expect { interface.draw }.to output("Zero or negative sizing data means this SVG cannot be rendered\n").to_stderr
end
end
context 'when log_warnings is not set' do
let(:interface) { Prawn::SVG::Interface.new(' ', prawn, { enable_web_requests: false }) }
it 'does not output warnings to stderr' do
expect { interface.draw }.not_to output.to_stderr
end
end
describe 'rewrites' do
before do
[:save_font, :bounding_box].each { |message| allow(prawn).to receive(message).and_yield }
allow(prawn).to receive_messages([:move_to, :line_to, :close_path, :fill_color, :stroke_color,
:transformation_matrix, :restore_graphics_state])
allow(prawn).to receive(:save_graphics_state) { |&block| block&.call }
end
context 'when fill_and_stroke is issued' do
def expect_rectangle
if RUBY_VERSION.start_with?('2.7.')
expect(prawn).to receive(:rectangle)
else
expect(prawn).to receive(:rectangle).with([0, 100], 10, 10)
end
end
context 'and fill rule is not set' do
let(:interface) do
Prawn::SVG::Interface.new(' ',
prawn, { enable_web_requests: false })
end
it "adds content 'f' then 'S'" do
expect_rectangle
expect(prawn).to receive(:rectangle).with([0, 100], 10, 10) # path replayed for stroke
expect(prawn).to receive(:add_content).with('W n')
expect(prawn).to receive(:add_content).with('f')
expect(prawn).to receive(:add_content).with('S')
interface.draw
end
end
context 'and fill rule is evenodd' do
let(:interface) do
Prawn::SVG::Interface.new(
' ', prawn, { enable_web_requests: false }
)
end
it "adds content 'f*' then 'S'" do
expect_rectangle
expect(prawn).to receive(:rectangle).with([0, 100], 10, 10) # path replayed for stroke
expect(prawn).to receive(:add_content).with('W n')
expect(prawn).to receive(:add_content).with('f*')
expect(prawn).to receive(:add_content).with('S')
interface.draw
end
end
end
end
end
describe '#position' do
subject { interface.position }
context 'when options[:at] supplied' do
let(:interface) { Prawn::SVG::Interface.new(svg, prawn, at: [1, 2], position: :left, enable_web_requests: false) }
it 'returns options[:at]' do
expect(subject).to eq [1, 2]
end
end
context 'when only a position is supplied' do
let(:interface) { Prawn::SVG::Interface.new(svg, prawn, position: position, enable_web_requests: false) }
context '(:left)' do
let(:position) { :left }
it { is_expected.to eq [0, 600] }
end
context '(:center)' do
let(:position) { :center }
it { is_expected.to eq [275, 600] }
end
context '(:right)' do
let(:position) { :right }
it { is_expected.to eq [550, 600] }
end
context 'a number' do
let(:position) { 25.5 }
it { is_expected.to eq [25.5, 600] }
end
end
context 'when a vposition is supplied' do
let(:interface) { Prawn::SVG::Interface.new(svg, prawn, vposition: vposition, enable_web_requests: false) }
context '(:top)' do
let(:vposition) { :top }
it { is_expected.to eq [0, 600] }
end
context '(:center)' do
let(:vposition) { :center }
it { is_expected.to eq [0, 350] }
end
context '(:bottom)' do
let(:vposition) { :bottom }
it { is_expected.to eq [0, 100] }
end
context 'a number' do
let(:vposition) { 25.5 }
it { is_expected.to eq [0, 600 - 25.5] }
end
end
end
describe '#sizing and #resize' do
let(:interface) { Prawn::SVG::Interface.new(svg, prawn, { enable_web_requests: false }) }
it 'allows the advanced user to resize the SVG after learning about its dimensions' do
expect(interface.sizing.output_width).to eq 250
expect(interface.sizing.output_height).to eq 100
interface.resize(width: 500)
expect(interface.sizing.output_width).to eq 500
expect(interface.sizing.output_height).to eq 200
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/length_spec.rb 0000664 0000000 0000000 00000005546 15155455534 0023111 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Length do
describe '.parse' do
it 'parses a length' do
expect(described_class.parse('1.23em')).to eq(described_class.new(1.23, :em))
end
it 'parses a length with a positive sign' do
expect(described_class.parse('+1.23em')).to eq(described_class.new(1.23, :em))
end
it 'parses a length with a negative sign' do
expect(described_class.parse('-1.23em')).to eq(described_class.new(-1.23, :em))
end
it 'parses a length with the unit in caps' do
expect(described_class.parse('1.23EM')).to eq(described_class.new(1.23, :em))
end
it 'parses a length with no decimal points' do
expect(described_class.parse('1em')).to eq(described_class.new(1, :em))
end
it 'parses a length with no unit' do
expect(described_class.parse('1.23')).to eq(described_class.new(1.23, nil))
end
it 'allows numbers without a leading zero' do
expect(described_class.parse('.23em')).to eq(described_class.new(0.23, :em))
end
it 'does not allow numbers with a trailing dot' do
expect(described_class.parse('1.em')).to be nil
end
it 'does not allow units it does not recognise' do
expect(described_class.parse('1.23foo')).to be nil
end
context 'when positive_only is true' do
it 'does not allow negative numbers' do
expect(described_class.parse('-1.23em', positive_only: true)).to be nil
end
it 'does allow zero' do
expect(described_class.parse('0em', positive_only: true)).to eq(described_class.new(0, :em))
end
it 'does allow positive numbers' do
expect(described_class.parse('1.23em', positive_only: true)).to eq(described_class.new(1.23, :em))
end
end
end
describe '#to_pixels' do
it 'converts a em-unit length to pixels' do
expect(described_class.new(2.5, :em).to_pixels(nil, 12)).to eq(12 * 2.5)
end
it 'converts a rem-unit length to pixels' do
expect(described_class.new(2.5, :rem).to_pixels(nil, 12)).to eq(16 * 2.5)
end
it 'converts a ex-unit length to pixels' do
expect(described_class.new(2.5, :ex).to_pixels(nil, 12)).to eq(6 * 2.5)
end
it 'converts a pc-unit length to pixels' do
expect(described_class.new(2.5, :pc).to_pixels(nil, 12)).to eq(37.5)
end
it 'converts a in-unit length to pixels' do
expect(described_class.new(2.5, :in).to_pixels(nil, 12)).to eq(180)
end
it 'converts a cm-unit length to pixels' do
expect(described_class.new(2.5, :cm).to_pixels(nil, 12)).to be_within(0.001).of(70.866)
end
it 'converts a mm-unit length to pixels' do
expect(described_class.new(2.5, :mm).to_pixels(nil, 12)).to be_within(0.001).of(7.087)
end
it 'returns the value for an unknown unit' do
expect(described_class.new(2.5, nil).to_pixels(nil, 12)).to eq(2.5)
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/loaders/ 0000775 0000000 0000000 00000000000 15155455534 0021710 5 ustar 00root root 0000000 0000000 mogest-prawn-svg-98d5f7a/spec/prawn/svg/loaders/data_spec.rb 0000664 0000000 0000000 00000002737 15155455534 0024171 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::Loaders::Data do
let(:uri) { URI(url) }
subject { Prawn::SVG::Loaders::Data.new.from_url(url) }
context 'with a valid image/png data URL' do
let(:url) { 'data:image/png;base64,aGVsbG8=' }
it 'loads the data' do
expect(subject).to eq 'hello'
end
end
context 'with a valid image/jpeg data URL' do
let(:url) { 'data:image/jpeg;base64,aGVsbG8=' }
it 'loads the data' do
expect(subject).to eq 'hello'
end
end
context 'with a data URL that has extra metadata' do
let(:url) { 'data:image/png;base64;metadata;here,aGVsbG8=' }
it 'loads the data' do
expect(subject).to eq 'hello'
end
end
context "with a data URL that's uppercase" do
let(:url) { 'DATA:IMAGE/PNG;BASE64;METADATA;HERE,aGVsbG8=' }
it 'loads the data' do
expect(subject).to eq 'hello'
end
end
context "with a URL that's not a data scheme" do
let(:url) { 'http://some.host' }
it 'returns nil' do
expect(subject).to be nil
end
end
context "with a data URL that's not an image" do
let(:url) { 'data:application/pdf;base64,aGVsbG8=' }
it 'raises' do
expect { subject }.to raise_error Prawn::SVG::UrlLoader::Error, /image/
end
end
context "with a data URL that's not base64 encoded" do
let(:url) { 'data:image/png;base32,agvsbg' }
it 'raises' do
expect { subject }.to raise_error Prawn::SVG::UrlLoader::Error, /base64/
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/loaders/file_spec.rb 0000664 0000000 0000000 00000010021 15155455534 0024160 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::Loaders::File do
let(:root_path) { '.' }
let(:fake_root_path) { '/some' }
let(:file_loader) { Prawn::SVG::Loaders::File.new(root_path) }
subject { file_loader.from_url(url) }
context 'when an invalid path is supplied' do
let(:root_path) { '/does/not/exist' }
it 'raises with an ArgumentError' do
expect { subject }.to raise_error ArgumentError, /is not a directory/
end
end
context 'when a relative path is supplied' do
let(:url) { 'relative/./path' }
it 'loads the file' do
expect(File).to receive(:expand_path).with('.').and_return(fake_root_path)
expect(File).to receive(:expand_path).with('relative/./path',
fake_root_path).and_return("#{fake_root_path}/relative/path")
expect(Dir).to receive(:exist?).with(fake_root_path).and_return(true)
expect(File).to receive(:exist?).with("#{fake_root_path}/relative/path").and_return(true)
expect(File).to receive(:binread).with("#{fake_root_path}/relative/path").and_return('data')
expect(subject).to eq 'data'
end
end
context 'when an absolute path without file scheme is supplied' do
let(:url) { '/some/absolute/./path' }
it 'loads the file' do
expect(File).to receive(:expand_path).with('.').and_return(fake_root_path)
expect(File).to receive(:expand_path).with(url, fake_root_path).and_return('/some/absolute/path')
expect(Dir).to receive(:exist?).with(fake_root_path).and_return(true)
expect(File).to receive(:exist?).with('/some/absolute/path').and_return(true)
expect(File).to receive(:binread).with('/some/absolute/path').and_return('data')
expect(subject).to eq 'data'
end
end
context 'when an absolute path with file scheme is supplied' do
let(:url) { 'file:///some/absolute/./path%20name' }
it 'loads the file' do
expect(File).to receive(:expand_path).with('.').and_return(fake_root_path)
expect(File).to receive(:expand_path).with('/some/absolute/./path name',
fake_root_path).and_return('/some/absolute/path name')
expect(Dir).to receive(:exist?).with(fake_root_path).and_return(true)
expect(File).to receive(:exist?).with('/some/absolute/path name').and_return(true)
expect(File).to receive(:binread).with('/some/absolute/path name').and_return('data')
expect(subject).to eq 'data'
end
end
context 'when a path outside of our root is specified' do
let(:url) { '/other/absolute/./path' }
it 'raises' do
expect(File).to receive(:expand_path).with('.').and_return(fake_root_path)
expect(File).to receive(:expand_path).with(url, fake_root_path).and_return('/other/absolute/path')
expect(Dir).to receive(:exist?).with(fake_root_path).and_return(true)
expect { subject }.to raise_error Prawn::SVG::UrlLoader::Error, /not inside the root path/
end
end
context 'when a file: url with a host is specified' do
let(:url) { 'file://somewhere/somefile' }
it 'raises' do
expect(File).to receive(:expand_path).with('.').and_return(fake_root_path)
expect(Dir).to receive(:exist?).with(fake_root_path).and_return(true)
expect { subject }.to raise_error Prawn::SVG::UrlLoader::Error, /with a host/
end
end
context "when we're running on Windows" do
let(:url) { 'file:///c:/path/to/file.png' }
let(:fake_root_path) { 'c:/full' }
it "automatically fixes up URI's misparsing of Windows file paths and loads the file" do
expect(File).to receive(:expand_path).with('.').and_return(fake_root_path)
expect(File).to receive(:expand_path).with('c:/path/to/file.png',
fake_root_path).and_return('c:/full/path/to/file.png')
expect(Dir).to receive(:exist?).with(fake_root_path).and_return(true)
expect(File).to receive(:exist?).with('c:/full/path/to/file.png').and_return(true)
expect(File).to receive(:binread).with('c:/full/path/to/file.png').and_return('data')
allow(file_loader).to receive(:windows?).and_return true
expect(subject).to eq 'data'
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/loaders/web_spec.rb 0000664 0000000 0000000 00000001750 15155455534 0024027 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::Loaders::Web do
let(:url) { 'http://hello.there/path' }
let(:uri) { URI(url) }
subject { Prawn::SVG::Loaders::Web.new.from_url(url) }
it 'loads an HTTP URL' do
expect(Net::HTTP).to receive(:get).with(uri).and_return('hello!')
expect(subject).to eq 'hello!'
end
context 'with an https URL' do
let(:url) { 'https://hello.there/path' }
it 'loads the HTTPS URL' do
expect(Net::HTTP).to receive(:get).with(uri).and_return('hello!')
expect(subject).to eq 'hello!'
end
end
context 'when the HTTP call raises' do
it 're-raises the error as UrlLoader errors' do
expect(Net::HTTP).to receive(:get).with(uri).and_raise(SocketError, 'argh')
expect { subject }.to raise_error Prawn::SVG::UrlLoader::Error, 'argh'
end
end
context 'with a non-http, non-https URL' do
let(:url) { 'mailto:someone@something' }
it 'returns nil' do
expect(subject).to be nil
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/paint_spec.rb 0000664 0000000 0000000 00000006455 15155455534 0022743 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::Paint do
let(:red) { Prawn::SVG::Color::RGB.new('ff0000') }
describe '.parse' do
it 'parses a color' do
expect(described_class.parse('red')).to eq(described_class.new(red, nil))
end
it 'parses an rgb color with an icc color, ignoring the icc color' do
expect(described_class.parse('rgb(255, 0, 0) icc-color(1)')).to eq(described_class.new(red, nil))
end
it 'parses a URL' do
expect(described_class.parse('url(#foo)')).to eq(described_class.new(:none, '#foo'))
end
it 'parses a keyword' do
expect(described_class.parse('NONE')).to eq(described_class.new(:none, nil))
expect(described_class.parse('currentColor')).to eq(described_class.new(:currentcolor, nil))
end
it 'parses a URL with a fallback keyword' do
expect(described_class.parse('url(#foo) none')).to eq(described_class.new(:none, '#foo'))
end
it 'parses a URL with a fallback color' do
expect(described_class.parse('url(#foo) red')).to eq(described_class.new(red, '#foo'))
end
it 'parses a URL with a fallback color and an icc color, ignoring the icc color' do
expect(described_class.parse('url(#foo) red icc-color(1)')).to eq(described_class.new(red, '#foo'))
end
it 'returns nil if the value is unrecognised' do
expect(described_class.parse('foo')).to be_nil
end
it 'returns nil if the url has multiple arguments' do
expect(described_class.parse('url(#foo, bar)')).to be_nil
end
end
describe '#none?' do
it 'returns true if the color is none' do
expect(described_class.new(:none, nil).none?).to be(true)
end
it 'returns true if the color is none and the URL is unresolved' do
paint = described_class.new(:none, '#foo')
paint.instance_variable_set(:@unresolved_url, true)
expect(paint.none?).to be(true)
end
it 'returns false if the color is not none' do
expect(described_class.new(Prawn::SVG::Color::RGB.new('ff0000'), nil).none?).to be(false)
end
end
describe '#resolve' do
it 'returns the current color if the color is currentcolor' do
current_color = double
paint = described_class.new(:currentcolor, nil)
expect(paint.resolve(nil, current_color, :rgb)).to eq current_color
end
it 'returns the nil if the color is none' do
current_color = double
paint = described_class.new(:none, nil)
expect(paint.resolve(nil, current_color, :rgb)).to be nil
end
it 'returns the gradient if the URL is resolvable' do
gradient = double
paint = described_class.new(:none, '#foo')
expect(paint.resolve({ 'foo' => gradient }, nil, :rgb)).to eq(gradient)
end
it 'falls back to the color if the URL is unresolvable' do
paint = described_class.new(red, '#foo')
expect(paint.resolve(nil, nil, :rgb)).to eq red
expect(paint.resolve({}, nil, :rgb)).to eq red
end
it 'returns the color if the color is not currentcolor or none' do
paint = described_class.new(red, nil)
expect(paint.resolve(nil, nil, :rgb)).to eq red
end
it 'converts the color to CMYK if the color mode is CMYK' do
paint = described_class.new(red, nil)
expect(paint.resolve(nil, nil, :cmyk)).to eq Prawn::SVG::Color::CMYK.new([0, 100, 100, 0])
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/pathable_spec.rb 0000664 0000000 0000000 00000005274 15155455534 0023406 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::Pathable do
class FakeElement < Prawn::SVG::Elements::Base
include Prawn::SVG::Pathable
def initialize(*args)
super
@properties = Struct.new(:marker_start, :marker_mid, :marker_end).new
end
public :apply_commands
public :apply_markers
def commands
@commands ||= [
Prawn::SVG::Pathable::Move.new([10, 10]),
Prawn::SVG::Pathable::Line.new([20, 20]),
Prawn::SVG::Pathable::Curve.new([30, 30], [25, 20], [25, 25]),
Prawn::SVG::Pathable::Close.new([10, 10])
]
end
end
let(:document) { Prawn::SVG::Document.new(' ', [800, 600], { width: 800, height: 600, enable_web_requests: false }) }
let(:state) { Prawn::SVG::State.new }
subject do
FakeElement.new(document, document.root, [], state)
end
describe '#bounding_box' do
it 'determines the bounding box using the translated commands' do
expect(subject.bounding_box).to eq [10, 590, 30, 570]
end
end
describe '#apply_commands' do
it 'applies the commands to the call stack' do
subject.apply_commands
expect(subject.base_calls).to eq [
['move_to', [[10.0, 590.0]], {}, []],
['line_to', [[20.0, 580.0]], {}, []],
['curve_to', [[30.0, 570.0]], { bounds: [[25.0, 580.0], [25.0, 575.0]] }, []],
['close_path', [], {}, []]
]
end
end
describe '#apply_markers' do
let(:marker) { instance_double(Prawn::SVG::Elements::Marker, name: 'marker') }
before do
document.elements_by_id['triangle'] = marker
end
context 'with marker-start attribute specified' do
before do
subject.properties.marker_start = Prawn::SVG::FuncIRI.new('#triangle')
end
it 'calls apply_marker on the marker' do
expect(marker).to receive(:apply_marker).with(subject, point: [10, 10], angle: -45.0)
subject.apply_markers
end
end
context 'with marker-mid attribute specified' do
before do
subject.properties.marker_mid = Prawn::SVG::FuncIRI.new('#triangle')
end
it 'calls apply_marker on the marker' do
expect(marker).to receive(:apply_marker).with(subject, point: [20, 20], angle: 45)
expect(marker).to receive(:apply_marker).with(subject, point: [30, 30], angle: -45)
subject.apply_markers
end
end
context 'with marker-end attribute specified' do
before do
subject.properties.marker_end = Prawn::SVG::FuncIRI.new('#triangle')
end
it 'calls apply_marker on the marker' do
expect(marker).to receive(:apply_marker).with(subject, point: [10, 10], angle: -45)
subject.apply_markers
end
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/pdfmatrix_spec.rb 0000664 0000000 0000000 00000003546 15155455534 0023624 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::PDFMatrix do
subject do
obj = Object.new
obj.extend(Prawn::SVG::PDFMatrix)
obj
end
describe '#load_matrix' do
it 'converts a PDF-style matrix into a Ruby Matrix' do
expect(subject.load_matrix([1.0, 0.0, 0.0, 1.0, 5.0, 7.0])).to eq(Matrix[
[1.0, 0.0, 5.0],
[0.0, 1.0, 7.0],
[0.0, 0.0, 1.0]
])
end
it 'returns the same Ruby Matrix if it has the correct dimensions' do
matrix = Matrix[[1.0, 0.0, 5.0], [0.0, 1.0, 7.0], [0.0, 0.0, 1.0]]
expect(subject.load_matrix(matrix)).to eq(matrix)
end
it 'raises an error for unexpected Ruby matrices' do
matrix = Matrix.identity(4)
expect { subject.load_matrix(matrix) }.to raise_error(ArgumentError)
end
it 'raises an error for unexpected PDF-style matrices' do
matrix = [1.0, 2.0, 3.0, 4.0]
expect { subject.load_matrix(matrix) }.to raise_error(ArgumentError)
end
end
describe '#matrix_for_pdf' do
it 'converts a Ruby Matrix into the correct format for PDF' do
matrix = Matrix[[1.0, 0.0, 5.0], [0.0, 1.0, 7.0], [0.0, 0.0, 1.0]]
expect(subject.matrix_for_pdf(matrix)).to eq([1.0, 0.0, 0.0, 1.0, 5.0, 7.0])
end
end
describe '#rotation_matrix' do
let(:angle) { 45 * Math::PI / 180.0 }
let(:inv_root_2) { 0.707 }
context 'in PDF space' do
it 'returns the expected matrix' do
matrix = Matrix[[inv_root_2, inv_root_2, 0], [-inv_root_2, inv_root_2, 0], [0, 0, 1]]
expect(subject.rotation_matrix(angle).round(3)).to eq(matrix)
end
end
context 'in SVG space' do
it 'returns the expected matrix' do
matrix = Matrix[[inv_root_2, -inv_root_2, 0], [inv_root_2, inv_root_2, 0], [0, 0, 1]]
expect(subject.rotation_matrix(angle, space: :svg).round(3)).to eq(matrix)
end
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/percentage_spec.rb 0000664 0000000 0000000 00000003457 15155455534 0023744 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::Percentage do
describe '.parse' do
it 'parses a percentage' do
expect(described_class.parse('1.23%')).to eq(described_class.new(1.23))
end
it 'parses a percentage with a positive sign' do
expect(described_class.parse('+1.23%')).to eq(described_class.new(1.23))
end
it 'parses a percentage with a negative sign' do
expect(described_class.parse('-1.23%')).to eq(described_class.new(-1.23))
end
it 'parses a percentage with no decimal points' do
expect(described_class.parse('1%')).to eq(described_class.new(1))
end
it 'does not parse a percentage with no number' do
expect(described_class.parse('%')).to be nil
end
it 'does not parse a percentage with a trailing dot' do
expect(described_class.parse('1.%')).to be nil
end
it 'requires that the percentage sign is specified' do
expect(described_class.parse('1.23')).to be nil
end
context 'when positive_only is true' do
it 'does not allow negative numbers' do
expect(described_class.parse('-1.23%', positive_only: true)).to be nil
end
it 'does allow zero' do
expect(described_class.parse('0%', positive_only: true)).to eq(described_class.new(0))
end
it 'does allow positive numbers' do
expect(described_class.parse('1.23%', positive_only: true)).to eq(described_class.new(1.23))
end
end
end
describe '#to_factor' do
it 'converts a percentage to a factor' do
expect(described_class.new(2.5).to_factor).to eq(0.025)
end
end
describe '#to_pixels' do
it 'converts a percentage to pixels' do
expect(described_class.new(2.5).to_pixels(100, nil)).to eq(2.5)
expect(described_class.new(2.5).to_pixels(200, nil)).to eq(5)
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/properties_spec.rb 0000664 0000000 0000000 00000032732 15155455534 0024021 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::Properties do
subject { Prawn::SVG::Properties.new }
describe '#load_default_stylesheet' do
it 'loads in the defaults and returns self' do
expect(subject.load_default_stylesheet).to eq subject
expect(subject.font_family).to eq 'sans-serif'
end
end
describe '#set' do
it 'sets a property' do
result = subject.set('color', 'red')
expect(result).to be
expect(subject.color).to eq Prawn::SVG::Color::RGB.new('ff0000')
end
it 'handles property names that are not lower case' do
result = subject.set('COLor', 'red')
expect(result).to be
expect(subject.color).to eq Prawn::SVG::Color::RGB.new('ff0000')
end
it 'right-cases and strips keywords' do
subject.set('stroke-linecap', ' Round ')
expect(subject.stroke_linecap).to eq 'round'
end
it 'ignores invalid values, retaining any previously set value' do
subject.set('display', 'invalid')
expect(subject.display).to be nil
subject.set('display', 'none')
expect(subject.display).to eq 'none'
subject.set('display', 'invalid')
expect(subject.display).to eq 'none'
end
context 'when setting the font shorthand property' do
it 'sets font style, weight, size, and family' do
subject.set('font', 'italic bold 12px/30px "New Font", sans-serif')
expect(subject.font_style).to eq 'italic'
expect(subject.font_weight).to eq 'bold'
expect(subject.font_variant).to eq 'normal'
expect(subject.font_size).to eq Prawn::SVG::Length.parse('12px')
expect(subject.font_family).to eq '"New Font", sans-serif'
end
it 'sets font style, weight, variant, size, and family' do
subject.set('font', 'italic small-caps bold 12px/30px "New Font", sans-serif')
expect(subject.font_style).to eq 'italic'
expect(subject.font_weight).to eq 'bold'
expect(subject.font_variant).to eq 'small-caps'
expect(subject.font_size).to eq Prawn::SVG::Length.parse('12px')
expect(subject.font_family).to eq '"New Font", sans-serif'
end
it 'sets size and family, defaulting everything else to normal' do
subject.set('font-weight', 'bold')
subject.set('font-style', 'italic')
subject.set('font-variant', 'small-caps')
subject.set('font-stretch', 'condensed')
subject.set('font', '12px fontname')
expect(subject.font_style).to eq 'normal'
expect(subject.font_weight).to eq 'normal'
expect(subject.font_variant).to eq 'normal'
expect(subject.font_stretch).to eq 'normal'
expect(subject.font_size).to eq Prawn::SVG::Length.parse('12px')
expect(subject.font_family).to eq 'fontname'
end
it 'suports the inherit keyword' do
subject.set('font', 'inherit')
expect(subject.font_style).to eq 'inherit'
expect(subject.font_weight).to eq 'inherit'
expect(subject.font_variant).to eq 'inherit'
expect(subject.font_stretch).to eq 'inherit'
expect(subject.font_size).to eq 'inherit'
expect(subject.font_family).to eq 'inherit'
end
it 'supports the standard font keyword, menu' do
subject.set('font', 'menu')
expect(subject.font_style).to eq 'normal'
expect(subject.font_weight).to eq 'normal'
expect(subject.font_variant).to eq 'normal'
expect(subject.font_stretch).to eq 'normal'
expect(subject.font_size).to eq 'medium'
expect(subject.font_family).to eq 'sans-serif'
end
it 'does nothing if an unrecognised keyword is used' do
subject.set('font', 'invalid')
expect(subject.font_style).to be nil
expect(subject.font_weight).to be nil
expect(subject.font_variant).to be nil
expect(subject.font_size).to be nil
expect(subject.font_family).to be nil
end
it 'does nothing if the font size is not valid' do
subject.set('font', '23bad sans-serif')
expect(subject.font_style).to be nil
expect(subject.font_weight).to be nil
expect(subject.font_variant).to be nil
expect(subject.font_size).to be nil
expect(subject.font_family).to be nil
end
it 'does nothing if one of the font keywords is not recognised' do
subject.set('font', 'italic invalid bold 12px/30px "New Font", sans-serif')
expect(subject.font_style).to be nil
expect(subject.font_weight).to be nil
expect(subject.font_variant).to be nil
expect(subject.font_size).to be nil
expect(subject.font_family).to be nil
end
end
context 'when setting the marker shorthand property' do
it 'sets marker-start, marker-mid, and marker-end to the same value' do
subject.set('marker', 'url(#arrow)')
expected = Prawn::SVG::FuncIRI.new('#arrow')
expect(subject.marker_start).to eq expected
expect(subject.marker_mid).to eq expected
expect(subject.marker_end).to eq expected
end
it 'sets all three to none' do
subject.set('marker-start', 'url(#arrow)')
subject.set('marker', 'none')
expect(subject.marker_start).to eq 'none'
expect(subject.marker_mid).to eq 'none'
expect(subject.marker_end).to eq 'none'
end
it 'does nothing for invalid values' do
subject.set('marker', 'invalid')
expect(subject.marker_start).to be nil
expect(subject.marker_mid).to be nil
expect(subject.marker_end).to be nil
end
end
end
describe '#load_hash' do
it 'uses #set to load in a hash of properties' do
subject.load_hash('stroke' => 'blue', 'fill' => 'green', 'stroke-linecap' => 'Round')
expect(subject.stroke).to eq Prawn::SVG::Paint.new(Prawn::SVG::Color::RGB.new('0000ff'))
expect(subject.fill).to eq Prawn::SVG::Paint.new(Prawn::SVG::Color::RGB.new('008000'))
expect(subject.stroke_linecap).to eq 'round'
end
end
describe '#compute_properties' do
let(:other) { Prawn::SVG::Properties.new }
it 'auto-inherits inheritable properties when the property is not supplied' do
subject.set('color', 'green')
subject.compute_properties(other)
expect(subject.color).to eq Prawn::SVG::Color::RGB.new('008000')
end
it "doesn't auto-inherit non-inheritable properties" do
subject.set('display', 'none')
subject.compute_properties(other)
expect(subject.display).to eq 'inline'
end
it 'inherits non-inheritable properties when specifically asked to' do
subject.set('display', 'none')
other.set('display', 'inherit')
subject.compute_properties(other)
expect(subject.display).to eq 'none'
end
it 'uses the new property value' do
subject.set('color', 'green')
other.set('color', 'red')
subject.compute_properties(other)
expect(subject.color).to eq Prawn::SVG::Color::RGB.new('ff0000')
end
end
describe '#numeric_font_size' do
def calculate
properties = Prawn::SVG::Properties.new
properties.compute_properties(subject)
properties.numeric_font_size
end
cases =
{
Prawn::SVG::Length.parse('18.5pt') => 18.5,
Prawn::SVG::Percentage.new(120) => 19.2,
19.5 => 19.5,
'larger' => 20,
'smaller' => 12,
nil => 16,
'inherit' => 16,
'x-large' => 24
}
cases.each do |font_size, expected|
context "when the font size is #{font_size.inspect}" do
before { subject.font_size = font_size }
it 'returns the correct number' do
expect(calculate).to eq expected
end
end
end
context 'with a font-size of 1.2em, under a parent with a font size of x-large' do
it 'returns 24 * 1.2' do
a = Prawn::SVG::Properties.new
a.set('font-size', 'x-large')
b = Prawn::SVG::Properties.new
b.set('font-size', '1.2em')
properties = Prawn::SVG::Properties.new
properties.compute_properties(a)
properties.compute_properties(b)
expect(properties.numeric_font_size.round(1)).to eq 28.8
end
end
end
describe 'font-weight bolder/lighter' do
it 'accepts bolder and lighter' do
subject.set('font-weight', 'bolder')
expect(subject.font_weight).to eq 'bolder'
subject.set('font-weight', 'lighter')
expect(subject.font_weight).to eq 'lighter'
end
context 'bolder keyword' do
{
'normal' => '700', '100' => '400', '200' => '400', '300' => '400',
'400' => '700', '500' => '700', '600' => '900', '700' => '900',
'800' => '900', '900' => '900', 'bold' => '900'
}.each do |parent_weight, expected|
it "resolves bolder from #{parent_weight} to #{expected}" do
parent = Prawn::SVG::Properties.new
parent.set('font-weight', parent_weight)
child = Prawn::SVG::Properties.new
child.set('font-weight', 'bolder')
computed = Prawn::SVG::Properties.new
computed.compute_properties(parent)
computed.compute_properties(child)
expect(computed.font_weight).to eq expected
end
end
end
context 'lighter keyword' do
{
'normal' => '100', '100' => '100', '200' => '100', '300' => '100',
'400' => '100', '500' => '100', '600' => '400', '700' => '400',
'800' => '700', '900' => '700', 'bold' => '400'
}.each do |parent_weight, expected|
it "resolves lighter from #{parent_weight} to #{expected}" do
parent = Prawn::SVG::Properties.new
parent.set('font-weight', parent_weight)
child = Prawn::SVG::Properties.new
child.set('font-weight', 'lighter')
computed = Prawn::SVG::Properties.new
computed.compute_properties(parent)
computed.compute_properties(child)
expect(computed.font_weight).to eq expected
end
end
end
it 'children inherit the resolved weight, not the relative keyword' do
grandparent = Prawn::SVG::Properties.new
grandparent.set('font-weight', '400')
parent = Prawn::SVG::Properties.new
parent.set('font-weight', 'bolder')
child = Prawn::SVG::Properties.new
child.set('font-weight', 'bolder')
computed = Prawn::SVG::Properties.new
computed.compute_properties(grandparent)
computed.compute_properties(parent)
expect(computed.font_weight).to eq '700'
computed.compute_properties(child)
expect(computed.font_weight).to eq '900'
end
end
describe 'font-stretch' do
it 'accepts absolute stretch keywords' do
%w[normal ultra-condensed extra-condensed condensed semi-condensed
semi-expanded expanded extra-expanded ultra-expanded].each do |value|
subject.set('font-stretch', value)
expect(subject.font_stretch).to eq value
end
end
it 'accepts wider and narrower' do
subject.set('font-stretch', 'wider')
expect(subject.font_stretch).to eq 'wider'
subject.set('font-stretch', 'narrower')
expect(subject.font_stretch).to eq 'narrower'
end
it 'rejects invalid values' do
subject.set('font-stretch', 'invalid')
expect(subject.font_stretch).to be_nil
end
it 'is inherited' do
parent = Prawn::SVG::Properties.new
parent.set('font-stretch', 'condensed')
child = Prawn::SVG::Properties.new
computed = Prawn::SVG::Properties.new
computed.compute_properties(parent)
computed.compute_properties(child)
expect(computed.font_stretch).to eq 'condensed'
end
context 'wider keyword' do
it 'resolves to the next expanded value from the parent' do
parent = Prawn::SVG::Properties.new
parent.set('font-stretch', 'normal')
child = Prawn::SVG::Properties.new
child.set('font-stretch', 'wider')
computed = Prawn::SVG::Properties.new
computed.compute_properties(parent)
computed.compute_properties(child)
expect(computed.font_stretch).to eq 'semi-expanded'
end
it 'caps at ultra-expanded' do
parent = Prawn::SVG::Properties.new
parent.set('font-stretch', 'ultra-expanded')
child = Prawn::SVG::Properties.new
child.set('font-stretch', 'wider')
computed = Prawn::SVG::Properties.new
computed.compute_properties(parent)
computed.compute_properties(child)
expect(computed.font_stretch).to eq 'ultra-expanded'
end
end
context 'narrower keyword' do
it 'resolves to the next condensed value from the parent' do
parent = Prawn::SVG::Properties.new
parent.set('font-stretch', 'normal')
child = Prawn::SVG::Properties.new
child.set('font-stretch', 'narrower')
computed = Prawn::SVG::Properties.new
computed.compute_properties(parent)
computed.compute_properties(child)
expect(computed.font_stretch).to eq 'semi-condensed'
end
it 'caps at ultra-condensed' do
parent = Prawn::SVG::Properties.new
parent.set('font-stretch', 'ultra-condensed')
child = Prawn::SVG::Properties.new
child.set('font-stretch', 'narrower')
computed = Prawn::SVG::Properties.new
computed.compute_properties(parent)
computed.compute_properties(child)
expect(computed.font_stretch).to eq 'ultra-condensed'
end
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/transform_parser_spec.rb 0000664 0000000 0000000 00000005255 15155455534 0025214 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::TransformParser do
class Test
include Prawn::SVG::Calculators::Pixels
include Prawn::SVG::TransformParser
State = Struct.new(:viewport_sizing)
Properties = Struct.new(:numeric_font_size)
Document = Struct.new(:sizing)
def document
Document.new(_sizing)
end
def state
State.new(_sizing)
end
def computed_properties
Properties.new(14)
end
def _sizing
Prawn::SVG::Calculators::DocumentSizing.new([1000, 800])
end
end
subject { Test.new.parse_transform_attribute(transform) }
context 'with no transform' do
let(:transform) { '' }
it { is_expected.to eq Matrix[[1, 0, 0], [0, 1, 0], [0, 0, 1]] }
end
context 'with translate' do
let(:transform) { 'translate(10 20)' }
it { is_expected.to eq Matrix[[1, 0, 10.0], [0, 1, -20.0], [0, 0, 1.0]] }
end
context 'with single argument translate' do
let(:transform) { 'translate(10)' }
it { is_expected.to eq Matrix[[1, 0, 10.0], [0, 1, 0.0], [0, 0, 1.0]] }
end
context 'with translateX' do
let(:transform) { 'translateX(10)' }
it { is_expected.to eq Matrix[[1, 0, 10.0], [0, 1, 0.0], [0, 0, 1.0]] }
end
context 'with translateY' do
let(:transform) { 'translateY(10)' }
it { is_expected.to eq Matrix[[1, 0, 0.0], [0, 1, -10.0], [0, 0, 1.0]] }
end
let(:sin30) { Math.sin(30 * Math::PI / 180.0) }
let(:cos30) { Math.cos(30 * Math::PI / 180.0) }
let(:tan30) { Math.tan(30 * Math::PI / 180.0) }
context 'with single argument rotate' do
let(:transform) { 'rotate(30)' }
it { is_expected.to eq Matrix[[cos30, sin30, 0], [-sin30, cos30, 0], [0.0, 0.0, 1]] }
end
context 'with triple argument rotate' do
let(:transform) { 'rotate(30 100 200)' }
it { is_expected.to eq Matrix[[cos30, sin30, 113.39745962155611], [-sin30, cos30, 23.205080756887753], [0.0, 0.0, 1.0]] }
end
context 'with scale' do
let(:transform) { 'scale(1.5)' }
it { is_expected.to eq Matrix[[1.5, 0.0, 0], [0.0, 1.5, 0], [0.0, 0.0, 1]] }
end
context 'with skewX' do
let(:transform) { 'skewX(30)' }
it { is_expected.to eq Matrix[[1, -tan30, 0], [0, 1.0, 0], [0, 0.0, 1]] }
end
context 'with skewY' do
let(:transform) { 'skewY(30)' }
it { is_expected.to eq Matrix[[1.0, 0, 0], [-tan30, 1, 0], [0.0, 0, 1]] }
end
context 'with matrix' do
let(:transform) { 'matrix(1 2 3 4 5 6)' }
it { is_expected.to eq Matrix[[1.0, -3.0, 5.0], [-2.0, 4.0, -6.0], [0.0, 0.0, 1.0]] }
end
context 'with multiple' do
let(:transform) { 'scale(2) translate(7) scale(3)' }
it { is_expected.to eq Matrix[[6.0, 0.0, 14.0], [0.0, 6.0, 0.0], [0.0, 0.0, 1.0]] }
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/ttc_spec.rb 0000664 0000000 0000000 00000001721 15155455534 0022411 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::TTC do
subject { Prawn::SVG::TTC.new(filename) }
context 'with a TrueType Collection font' do
let(:filename) { "#{File.dirname(__FILE__)}/../../sample_ttf/TestFamily.ttc" }
it 'extracts family, subfamily, index, and weight class for each font in the collection' do
expect(subject.fonts.length).to eq 2
expect(subject.fonts[0]).to include(family: 'Test Family', subfamily: 'Regular', index: 0)
expect(subject.fonts[1]).to include(family: 'Test Family', subfamily: 'Bold', index: 1)
end
end
context "with a file that isn't a TTC" do
let(:filename) { "#{File.dirname(__FILE__)}/../../sample_ttf/OpenSans-SemiboldItalic.ttf" }
it 'returns no fonts' do
expect(subject.fonts).to be_empty
end
end
context "with a file that doesn't exist" do
let(:filename) { 'does_not_exist' }
it 'returns no fonts' do
expect(subject.fonts).to be_empty
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/ttf_spec.rb 0000664 0000000 0000000 00000001543 15155455534 0022416 0 ustar 00root root 0000000 0000000 require 'spec_helper'
RSpec.describe Prawn::SVG::TTF do
subject { Prawn::SVG::TTF.new(filename) }
context 'with a truetype font' do
let(:filename) { "#{File.dirname(__FILE__)}/../../sample_ttf/OpenSans-SemiboldItalic.ttf" }
it 'gets the English family and subfamily from the font file' do
expect(subject.family).to eq 'Open Sans'
expect(subject.subfamily).to eq 'Semibold Italic'
end
end
context "with a file that isn't a TTF" do
let(:filename) { __FILE__ }
it 'has a nil family and subfamily' do
expect(subject.family).to be nil
expect(subject.subfamily).to be nil
end
end
context "with a file that doesn't exist" do
let(:filename) { 'does_not_exist' }
it 'has a nil family and subfamily' do
expect(subject.family).to be nil
expect(subject.subfamily).to be nil
end
end
end
mogest-prawn-svg-98d5f7a/spec/prawn/svg/url_loader_spec.rb 0000664 0000000 0000000 00000007470 15155455534 0023756 0 ustar 00root root 0000000 0000000 require 'spec_helper'
describe Prawn::SVG::UrlLoader do
let(:enable_cache) { true }
let(:enable_web) { true }
let(:enable_file) { '.' }
let(:loader) do
Prawn::SVG::UrlLoader.new(enable_cache: enable_cache, enable_web: enable_web, enable_file_with_root: enable_file)
end
describe '#initialize' do
it 'sets options' do
expect(loader.enable_cache).to be true
end
end
describe '#load' do
let(:url) { 'http://hello/there' }
let(:data_loader) { instance_double(Prawn::SVG::Loaders::Data) }
let(:web_loader) { instance_double(Prawn::SVG::Loaders::Web) }
let(:file_loader) { instance_double(Prawn::SVG::Loaders::File) }
before do
allow(Prawn::SVG::Loaders::Data).to receive(:new).and_return(data_loader)
allow(Prawn::SVG::Loaders::Web).to receive(:new).and_return(web_loader)
allow(Prawn::SVG::Loaders::File).to receive(:new).with(enable_file).and_return(file_loader)
end
subject { loader.load(url) }
it 'calls the Data loader and returns its output if successful' do
expect(data_loader).to receive(:from_url).with(url).and_return('data')
expect(web_loader).not_to receive(:from_url)
expect(subject).to eq 'data'
end
it 'calls the Web loader if the Data loader returns nothing, and returns its output if successful' do
expect(data_loader).to receive(:from_url).with(url)
expect(web_loader).to receive(:from_url).with(url).and_return('data')
expect(subject).to eq 'data'
end
it 'calls the File loader if the Data and Web loaders return nothing, and returns its output if successful' do
expect(data_loader).to receive(:from_url).with(url)
expect(web_loader).to receive(:from_url).with(url)
expect(file_loader).to receive(:from_url).with(url).and_return('data')
expect(subject).to eq 'data'
end
it 'raises if none of the loaders return any data' do
expect(data_loader).to receive(:from_url).with(url)
expect(web_loader).to receive(:from_url).with(url)
expect(file_loader).to receive(:from_url).with(url)
expect { subject }.to raise_error(Prawn::SVG::UrlLoader::Error, /No handler available/)
end
context 'when caching is enabled' do
it 'caches the result' do
expect(data_loader).to receive(:from_url).with(url).and_return('data')
expect(subject).to eq 'data'
expect(loader.retrieve_from_cache(url)).to eq 'data'
end
end
context 'when caching is disabled' do
let(:enable_cache) { false }
it 'does not cache the result' do
expect(data_loader).to receive(:from_url).with(url).and_return('data')
expect(subject).to eq 'data'
expect(loader.retrieve_from_cache(url)).to be nil
end
end
context 'when the cache is populated' do
before { loader.add_to_cache(url, 'data') }
it 'returns the cached value without calling a loader' do
expect(data_loader).not_to receive(:from_url)
expect(web_loader).not_to receive(:from_url)
expect(subject).to eq 'data'
end
end
context 'when web requests are disabled' do
let(:enable_web) { false }
it "doesn't use the web loader" do
expect(data_loader).to receive(:from_url)
expect(web_loader).not_to receive(:from_url)
expect(file_loader).to receive(:from_url)
expect { subject }.to raise_error(Prawn::SVG::UrlLoader::Error, /No handler available/)
end
end
context 'when file requests are disabled' do
let(:enable_file) { nil }
it "doesn't use the file loader" do
expect(data_loader).to receive(:from_url)
expect(web_loader).to receive(:from_url)
expect(file_loader).not_to receive(:from_url)
expect { subject }.to raise_error(Prawn::SVG::UrlLoader::Error, /No handler available/)
end
end
end
end
mogest-prawn-svg-98d5f7a/spec/sample_css/ 0000775 0000000 0000000 00000000000 15155455534 0020462 5 ustar 00root root 0000000 0000000 mogest-prawn-svg-98d5f7a/spec/sample_css/import_base.css 0000664 0000000 0000000 00000000213 15155455534 0023474 0 ustar 00root root 0000000 0000000 @import url("sample_css/import_nested.css");
.imported { fill: #336699; stroke: #003366; stroke-width: 2; }
.base-only { fill: #cc6600; }
mogest-prawn-svg-98d5f7a/spec/sample_css/import_nested.css 0000664 0000000 0000000 00000000077 15155455534 0024054 0 ustar 00root root 0000000 0000000 .nested { fill: #669933; stroke: #336600; stroke-width: 1.5; }
mogest-prawn-svg-98d5f7a/spec/sample_images/ 0000775 0000000 0000000 00000000000 15155455534 0021137 5 ustar 00root root 0000000 0000000 mogest-prawn-svg-98d5f7a/spec/sample_images/image_svg_embed.svg 0000664 0000000 0000000 00000000545 15155455534 0024761 0 ustar 00root root 0000000 0000000
mogest-prawn-svg-98d5f7a/spec/sample_images/mushroom-long.jpg 0000664 0000000 0000000 00000055643 15155455534 0024464 0 ustar 00root root 0000000 0000000 JFIF H H ICC_PROFILE mntrRGB XYZ $ acsp - )=ޯUxBʃ9
desc D ybXYZ bTRC dmdd gXYZ
h gTRC lumi
| meas
$bkpt
rXYZ
rTRC tech
vued
wtpt p cprt 7chad ,desc sRGB IEC61966-2-1 black scaled XYZ $ curv
# ( - 2 7 ; @ E J O T Y ^ c h m r w |
%+28>ELRY`gnu|&/8AKT]gqz !-8COZfr~ -;HUcq~
+:IXgw'7HYj{+=Oat2FZn % : O d y
'
=
T
j
"9Qi*C\u
&
@
Z
t
.Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i A l !!H!u!!!"'"U"""#
#8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''(
(?(q(())8)k))**5*h**++6+i++,,9,n,,--A-v--..L.../$/Z///050l0011J1112*2c223
3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JKKSKKL*LrLMMJMMN%NnNO OIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G
k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4
uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD- u`ֲK³8%yhYѹJº;.!
zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs
2F[p(@Xr4Pm8Ww)Kmdesc .IEC 61966-2-1 Default RGB Colour Space - sRGB XYZ b XYZ P meas XYZ 3 XYZ o 8 sig CRT desc -Reference Viewing Condition in IEC 61966-2-1 XYZ -text Copyright International Color Consortium, 2009 sf32 D &