Skip to content

Commit 8d894ac

Browse files
authored
Merge pull request #31 from blocknotes/css-initial-values
Handle CSS `initial` values
2 parents 18459b3 + 3231abe commit 8d894ac

12 files changed

Lines changed: 126 additions & 45 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ CSS attributes (dimensional units are ignored and considered in pixel):
9090
- **top**: see *position (absolute)*
9191
- **width**: for *img* tag, support also percentage, ex. `<img src="image.jpg" style="width: 50%; height: 200px"/>`
9292

93+
The above attributes supports the `initial` value to reset them to their original value.
94+
9395
For colors, the supported formats are:
9496
- 3 hex digits, ex. `color: #FB1`;
9597
- 6 hex digits, ex. `color: #abcdef`;

lib/prawn_html/attributes.rb

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# frozen_string_literal: true
22

33
require 'ostruct'
4+
require 'set'
45

56
module PrawnHtml
67
class Attributes < OpenStruct
7-
attr_reader :styles
8+
attr_reader :initial, :styles
89

910
STYLES_APPLY = {
1011
block: %i[align bottom leading left margin_left padding_left position right top],
@@ -19,13 +20,13 @@ class Attributes < OpenStruct
1920
'color' => { key: :color, set: :convert_color },
2021
'font-family' => { key: :font, set: :unquote },
2122
'font-size' => { key: :size, set: :convert_size },
22-
'font-style' => { key: :styles, set: :append_styles },
23-
'font-weight' => { key: :styles, set: :append_styles },
23+
'font-style' => { key: :styles, set: :append_styles, values: %i[italic] },
24+
'font-weight' => { key: :styles, set: :append_styles, values: %i[bold] },
2425
'href' => { key: :link, set: :copy_value },
2526
'letter-spacing' => { key: :character_spacing, set: :convert_float },
2627
'list-style-type' => { key: :list_style_type, set: :unquote },
27-
'text-decoration' => { key: :styles, set: :append_text_decoration },
28-
'vertical-align' => { key: :styles, set: :append_styles },
28+
'text-decoration' => { key: :styles, set: :append_styles, values: %i[underline] },
29+
'vertical-align' => { key: :styles, set: :append_styles, values: %i[subscript superscript] },
2930
'white-space' => { key: :white_space, set: :convert_symbol },
3031
# tag opening styles
3132
'break-before' => { key: :break_before, set: :convert_symbol },
@@ -44,7 +45,9 @@ class Attributes < OpenStruct
4445
'position' => { key: :position, set: :convert_symbol },
4546
'right' => { key: :right, set: :convert_size, options: :width },
4647
'text-align' => { key: :align, set: :convert_symbol },
47-
'top' => { key: :top, set: :convert_size, options: :height }
48+
'top' => { key: :top, set: :convert_size, options: :height },
49+
# special styles
50+
'text-decoration-line-through' => { key: :callback, set: :callback_strike_through }
4851
}.freeze
4952

5053
STYLES_MERGE = %i[margin_left padding_left].freeze
@@ -53,6 +56,7 @@ class Attributes < OpenStruct
5356
def initialize(attributes = {})
5457
super
5558
@styles = {} # result styles
59+
@initial = Set.new
5660
end
5761

5862
# Processes the data attributes
@@ -74,6 +78,33 @@ def merge_text_styles!(text_styles, options: {})
7478
process_styles(hash_styles, options: options) unless hash_styles.empty?
7579
end
7680

81+
# Remove an attribute value from the context styles
82+
#
83+
# @param context_styles [Hash] hash of the context styles that will be updated
84+
# @param rule [Hash] rule from the STYLES_LIST to lookup in the context style for value removal
85+
def remove_value(context_styles, rule)
86+
if rule[:set] == :append_styles
87+
context_styles[rule[:key]] -= rule[:values] if context_styles[:styles]
88+
else
89+
default = Context::DEFAULT_STYLES[rule[:key]]
90+
default ? (context_styles[rule[:key]] = default) : context_styles.delete(rule[:key])
91+
end
92+
end
93+
94+
# Update context styles applying the initial rules (if set)
95+
#
96+
# @param context_styles [Hash] hash of the context styles that will be updated
97+
#
98+
# @return [Hash] the update context styles
99+
def update_styles(context_styles)
100+
initial.each do |rule|
101+
next unless rule
102+
103+
remove_value(context_styles, rule)
104+
end
105+
context_styles
106+
end
107+
77108
class << self
78109
# Merges attributes
79110
#
@@ -105,29 +136,30 @@ def parse_styles(styles)
105136
def process_styles(hash_styles, options:)
106137
hash_styles.each do |key, value|
107138
rule = evaluate_rule(key, value)
139+
next unless rule
140+
108141
apply_rule!(merged_styles: @styles, rule: rule, value: value, options: options)
109142
end
110143
@styles
111144
end
112145

113146
def evaluate_rule(rule_key, attr_value)
114-
rule = STYLES_LIST[rule_key]
115-
if rule && rule[:set] == :append_text_decoration
116-
return { key: :callback, set: :callback_strike_through } if attr_value == 'line-through'
117-
118-
return { key: :styles, set: :append_styles }
119-
end
120-
rule
147+
key = nil
148+
key = 'text-decoration-line-through' if rule_key == 'text-decoration' && attr_value == 'line-through'
149+
key ||= rule_key
150+
STYLES_LIST[key]
121151
end
122152

123153
def apply_rule!(merged_styles:, rule:, value:, options:)
124-
return unless rule
154+
return (@initial << rule) if value == 'initial'
125155

126156
if rule[:set] == :append_styles
127-
(merged_styles[rule[:key]] ||= []) << Utils.normalize_style(value)
157+
val = Utils.normalize_style(value)
158+
(merged_styles[rule[:key]] ||= []) << val if val
128159
else
129160
opts = rule[:options] ? options[rule[:options]] : nil
130-
merged_styles[rule[:key]] = Utils.send(rule[:set], value, options: opts)
161+
val = Utils.send(rule[:set], value, options: opts)
162+
merged_styles[rule[:key]] = val if val
131163
end
132164
end
133165
end

lib/prawn_html/context.rb

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
module PrawnHtml
44
class Context < Array
5-
DEF_FONT_SIZE = 16 * PX
5+
DEFAULT_STYLES = {
6+
size: 16 * PX
7+
}.freeze
68

79
attr_reader :previous_tag
810
attr_accessor :last_text_node
@@ -54,9 +56,9 @@ def block_styles
5456
# @return [Hash] the hash of merged styles
5557
def merged_styles
5658
@merged_styles ||=
57-
each_with_object(base_styles) do |element, res|
59+
each_with_object(DEFAULT_STYLES.dup) do |element, res|
5860
evaluate_element_styles(element, res)
59-
element.update_styles(res) if element.respond_to?(:update_styles)
61+
element.update_styles(res)
6062
end
6163
end
6264

@@ -71,12 +73,6 @@ def remove_last
7173

7274
private
7375

74-
def base_styles
75-
{
76-
size: DEF_FONT_SIZE
77-
}
78-
end
79-
8076
def evaluate_element_styles(element, res)
8177
styles = element.styles.slice(*Attributes::STYLES_APPLY[:text_node])
8278
styles.each do |key, val|

lib/prawn_html/document_renderer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ def apply_callbacks(buffer)
129129
def adjust_leading(buffer, leading)
130130
return leading if leading
131131

132-
(buffer.map { |item| item[:size] || Context::DEF_FONT_SIZE }.max * 0.055).round(4)
132+
(buffer.map { |item| item[:size] || Context::DEFAULT_STYLES[:size] }.max * 0.055).round(4)
133133
end
134134

135135
def bounds(buffer, options, block_styles)

lib/prawn_html/tag.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22

33
module PrawnHtml
44
class Tag
5+
extend Forwardable
6+
57
CALLBACKS = {
68
'Background' => Callbacks::Background,
79
'StrikeThrough' => Callbacks::StrikeThrough
810
}.freeze
11+
912
TAG_CLASSES = %w[A B Blockquote Body Br Code Del Div H Hr I Img Li Mark Ol P Pre Small Span Sub Sup U Ul].freeze
1013

14+
def_delegators :@attrs, :styles, :update_styles
15+
1116
attr_accessor :parent
1217
attr_reader :attrs, :tag
1318

@@ -55,13 +60,6 @@ def tag_close_styles
5560
styles.slice(*Attributes::STYLES_APPLY[:tag_close])
5661
end
5762

58-
# Styles hash
59-
#
60-
# @return [Hash] hash of styles
61-
def styles
62-
attrs.styles
63-
end
64-
6563
# Styles to apply on tag opening
6664
#
6765
# @return [Hash] hash of styles to apply

lib/prawn_html/tags/small.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ module Tags
55
class Small < Tag
66
ELEMENTS = [:small].freeze
77

8-
def update_styles(styles)
9-
size = (styles[:size] || Context::DEF_FONT_SIZE) * 0.85
10-
styles[:size] = size
11-
styles
8+
def update_styles(context_styles)
9+
size = (context_styles[:size] || Context::DEFAULT_STYLES[:size]) * 0.85
10+
context_styles[:size] = size
11+
super(context_styles)
1212
end
1313
end
1414
end

spec/integrations/blocks_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
RSpec.describe 'Blocks' do
44
let(:pdf) { Prawn::Document.new(page_size: 'A4', page_layout: :portrait) }
5-
let(:size) { PrawnHtml::Context::DEF_FONT_SIZE }
5+
let(:size) { PrawnHtml::Context::DEFAULT_STYLES[:size] }
66

77
before do
88
PrawnHtml.append_html(pdf, html)

spec/support/test_utils.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
module TestUtils
44
extend self
55

6-
def adjust_leading(size = PrawnHtml::Context::DEF_FONT_SIZE)
6+
def adjust_leading(size = PrawnHtml::Context::DEFAULT_STYLES[:size])
77
(size * 0.055).round(4)
88
end
99

@@ -16,7 +16,7 @@ def default_font_family
1616
end
1717

1818
def default_font_size
19-
PrawnHtml::Context::DEF_FONT_SIZE
19+
PrawnHtml::Context::DEFAULT_STYLES[:size]
2020
end
2121

2222
def font_ascender(font_family: 'Helvetica', font_size: default_font_size)

spec/units/prawn_html/attributes_spec.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,44 @@
7171
end
7272
end
7373

74+
describe '#remove_value' do
75+
subject(:remove_value) { attributes.remove_value(context_styles, rule) }
76+
77+
let(:context_styles) { { size: 9.6, styles: %i[bold italic] } }
78+
79+
context 'with a missing rule' do
80+
let(:rule) { { key: :color, set: :convert_color } }
81+
82+
it "doesn't change the context styles" do
83+
expect { remove_value }.not_to change(context_styles, :values)
84+
end
85+
end
86+
87+
context 'with an applied rule' do
88+
let(:rule) { { key: :styles, set: :append_styles, values: %i[bold] } }
89+
90+
it "changes the context styles" do
91+
expect { remove_value }.to change(context_styles, :values).from([9.6, %i[bold italic]]).to([9.6, %i[italic]])
92+
end
93+
end
94+
end
95+
96+
describe '#update_styles' do
97+
subject(:update_styles) { attributes.update_styles(context_styles) }
98+
99+
let(:context_styles) { { size: 9.6 } }
100+
let(:rule) { { key: :styles, set: :append_styles, values: %i[bold] } }
101+
102+
before do
103+
allow(attributes).to receive_messages(initial: Set.new([rule]), remove_value: nil)
104+
end
105+
106+
it 'asks to the attributes to remove the value from the context styles that match the specified rule' do
107+
update_styles
108+
expect(attributes).to have_received(:remove_value).with(context_styles, rule)
109+
end
110+
end
111+
74112
describe '.merge_attr!' do
75113
context 'with an empty key' do
76114
let(:key) { nil }

spec/units/prawn_html/context_spec.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,12 @@ def on_context_remove(context)
121121
subject(:merged_styles) { context.merged_styles }
122122

123123
context 'with no elements' do
124-
it { is_expected.to eq(size: PrawnHtml::Context::DEF_FONT_SIZE) }
124+
it { is_expected.to eq(size: PrawnHtml::Context::DEFAULT_STYLES[:size]) }
125125
end
126126

127127
context 'with some elements' do
128-
let(:tag1) { instance_double(PrawnHtml::Tag, styles: { color: 'fb1', size: 12.34 }) }
129-
let(:tag2) { instance_double(PrawnHtml::Tag, styles: { color: 'abc' }) }
128+
let(:tag1) { instance_double(PrawnHtml::Tag, styles: { color: 'fb1', size: 12.34 }, update_styles: nil) }
129+
let(:tag2) { instance_double(PrawnHtml::Tag, styles: { color: 'abc' }, update_styles: nil) }
130130

131131
before do
132132
context << tag1 << tag2
@@ -145,7 +145,7 @@ def update_styles(res)
145145
end
146146
end
147147
end
148-
let(:tag1) { instance_double(PrawnHtml::Tag, styles: { color: 'fb1', size: 12.34 }) }
148+
let(:tag1) { instance_double(PrawnHtml::Tag, styles: { color: 'fb1', size: 12.34 }, update_styles: nil) }
149149
let(:tag2) { some_tag_class.new(:some_tag) }
150150

151151
before do

0 commit comments

Comments
 (0)