thinreports 0.8.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/CONTRIBUTING.md +2 -0
- data/.travis.yml +3 -3
- data/CHANGELOG.md +98 -1
- data/README.md +0 -9
- data/Rakefile +1 -2
- data/examples/character_spacing/character_spacing.tlf +293 -1
- data/examples/dynamic_image/dynamic_image.tlf +661 -1
- data/examples/dynamic_style/dynamic_style.rb +4 -4
- data/examples/dynamic_style/dynamic_style.tlf +1835 -1
- data/examples/dynamic_style/dynamic_style_in_list.tlf +344 -1
- data/examples/eudc/eudc.tlf +180 -1
- data/examples/hidden_shapes/hidden_shapes.tlf +449 -1
- data/examples/list_events/list_events.rb +1 -28
- data/examples/list_events/list_events.tlf +361 -0
- data/examples/list_manual_generation/list_manual_generation.tlf +132 -1
- data/examples/list_page_number/list_page_number.tlf +254 -1
- data/examples/page_number/page_number.tlf +215 -1
- data/examples/palleted_png/palleted_png.rb +2 -0
- data/examples/palleted_png/palleted_png.tlf +47 -1
- data/examples/password_setting/password_setting.tlf +45 -1
- data/examples/report_callbacks/report_callbacks.rb +1 -18
- data/examples/report_callbacks/report_callbacks.tlf +147 -1
- data/examples/single_line_tblock/single_line_tblock.tlf +170 -1
- data/examples/tblock_overflow/tblock_overflow.tlf +538 -1
- data/examples/tblock_styles/font_size.tlf +383 -1
- data/examples/tblock_styles/tblock_styles.tlf +889 -1
- data/examples/text_align/text_align.tlf +241 -1
- data/examples/typeB_page_size/B4_ISO.tlf +45 -1
- data/examples/typeB_page_size/B4_JIS.tlf +45 -1
- data/examples/word_wrap/word_wrap.tlf +297 -1
- data/lib/thinreports.rb +0 -1
- data/lib/thinreports/config.rb +3 -0
- data/lib/thinreports/core/errors.rb +0 -3
- data/lib/thinreports/core/format/base.rb +4 -4
- data/lib/thinreports/core/shape.rb +1 -9
- data/lib/thinreports/core/shape/basic.rb +3 -3
- data/lib/thinreports/core/shape/basic/block_format.rb +1 -1
- data/lib/thinreports/core/shape/basic/format.rb +2 -14
- data/lib/thinreports/core/shape/basic/internal.rb +2 -2
- data/lib/thinreports/core/shape/image_block.rb +3 -3
- data/lib/thinreports/core/shape/image_block/format.rb +0 -2
- data/lib/thinreports/core/shape/image_block/internal.rb +1 -1
- data/lib/thinreports/core/shape/list.rb +3 -7
- data/lib/thinreports/core/shape/list/format.rb +37 -24
- data/lib/thinreports/core/shape/list/manager.rb +1 -36
- data/lib/thinreports/core/shape/list/page.rb +0 -15
- data/lib/thinreports/core/shape/list/page_state.rb +1 -1
- data/lib/thinreports/core/shape/list/section_format.rb +14 -12
- data/lib/thinreports/core/shape/list/section_internal.rb +4 -7
- data/lib/thinreports/core/shape/manager/format.rb +0 -2
- data/lib/thinreports/core/shape/manager/internal.rb +1 -1
- data/lib/thinreports/core/shape/page_number.rb +1 -1
- data/lib/thinreports/core/shape/page_number/format.rb +9 -6
- data/lib/thinreports/core/shape/page_number/interface.rb +1 -1
- data/lib/thinreports/core/shape/page_number/internal.rb +2 -6
- data/lib/thinreports/core/shape/style/base.rb +11 -13
- data/lib/thinreports/core/shape/style/basic.rb +4 -4
- data/lib/thinreports/core/shape/style/graphic.rb +4 -10
- data/lib/thinreports/core/shape/style/text.rb +36 -69
- data/lib/thinreports/core/shape/text.rb +3 -3
- data/lib/thinreports/core/shape/text/format.rb +3 -13
- data/lib/thinreports/core/shape/text/internal.rb +2 -2
- data/lib/thinreports/core/shape/text_block.rb +3 -3
- data/lib/thinreports/core/shape/text_block/format.rb +9 -6
- data/lib/thinreports/core/shape/text_block/interface.rb +7 -7
- data/lib/thinreports/core/shape/text_block/internal.rb +1 -1
- data/lib/thinreports/generator/pdf.rb +0 -1
- data/lib/thinreports/generator/pdf/document.rb +2 -2
- data/lib/thinreports/generator/pdf/document/draw_shape.rb +25 -51
- data/lib/thinreports/generator/pdf/document/draw_template_items.rb +66 -0
- data/lib/thinreports/generator/pdf/document/font.rb +3 -8
- data/lib/thinreports/generator/pdf/document/graphics/attributes.rb +97 -77
- data/lib/thinreports/generator/pdf/document/graphics/basic.rb +18 -13
- data/lib/thinreports/generator/pdf/document/graphics/image.rb +17 -12
- data/lib/thinreports/generator/pdf/document/page.rb +1 -1
- data/lib/thinreports/generator/pdf/drawer/list.rb +5 -1
- data/lib/thinreports/generator/pdf/drawer/list_section.rb +1 -4
- data/lib/thinreports/generator/pdf/drawer/page.rb +9 -9
- data/lib/thinreports/layout.rb +3 -3
- data/lib/thinreports/layout/base.rb +0 -16
- data/lib/thinreports/layout/format.rb +38 -57
- data/lib/thinreports/layout/legacy_schema.rb +354 -0
- data/lib/thinreports/layout/version.rb +1 -1
- data/lib/thinreports/report.rb +0 -1
- data/lib/thinreports/report/base.rb +3 -17
- data/lib/thinreports/report/internal.rb +13 -55
- data/lib/thinreports/report/page.rb +1 -1
- data/lib/thinreports/version.rb +1 -1
- data/test/data/legacy_layout/all-items.tlf +1 -0
- data/test/schema_helper.rb +121 -0
- data/test/test_helper.rb +13 -50
- data/test/unit/core/format/test_base.rb +35 -31
- data/test/unit/core/shape/base/test_internal.rb +1 -1
- data/test/unit/core/shape/basic/test_block_format.rb +23 -0
- data/test/unit/core/shape/basic/{test_basic_interface.rb → test_block_interface.rb} +1 -1
- data/test/unit/core/shape/basic/{test_basic_internal.rb → test_block_internal.rb} +4 -7
- data/test/unit/core/shape/basic/test_format.rb +25 -47
- data/test/unit/core/shape/basic/test_interface.rb +5 -5
- data/test/unit/core/shape/basic/test_internal.rb +6 -11
- data/test/unit/core/shape/image_block/test_interface.rb +5 -5
- data/test/unit/core/shape/image_block/test_internal.rb +2 -2
- data/test/unit/core/shape/list/test_format.rb +86 -44
- data/test/unit/core/shape/list/test_manager.rb +10 -65
- data/test/unit/core/shape/list/test_page.rb +10 -61
- data/test/unit/core/shape/list/test_page_state.rb +1 -1
- data/test/unit/core/shape/list/test_section_format.rb +19 -28
- data/test/unit/core/shape/list/test_section_interface.rb +14 -6
- data/test/unit/core/shape/list/test_section_internal.rb +17 -23
- data/test/unit/core/shape/manager/test_format.rb +1 -1
- data/test/unit/core/shape/manager/test_internal.rb +59 -98
- data/test/unit/core/shape/manager/test_target.rb +21 -30
- data/test/unit/core/shape/page_number/test_format.rb +34 -52
- data/test/unit/core/shape/page_number/test_interface.rb +1 -1
- data/test/unit/core/shape/page_number/test_internal.rb +2 -2
- data/test/unit/core/shape/styles/test_base.rb +16 -41
- data/test/unit/core/shape/styles/test_basic.rb +7 -7
- data/test/unit/core/shape/styles/test_graphic.rb +27 -32
- data/test/unit/core/shape/styles/test_text.rb +68 -311
- data/test/unit/core/shape/text/test_format.rb +30 -59
- data/test/unit/core/shape/text/test_internal.rb +5 -5
- data/test/unit/core/shape/text_block/formatter/test_datetime.rb +13 -13
- data/test/unit/core/shape/text_block/test_format.rb +145 -146
- data/test/unit/core/shape/text_block/test_interface.rb +9 -9
- data/test/unit/core/shape/text_block/test_internal.rb +9 -9
- data/test/unit/core/test_shape.rb +19 -31
- data/test/unit/generator/pdf/document/graphics/test_attributes.rb +112 -122
- data/test/unit/generator/pdf/document/graphics/test_basic.rb +45 -0
- data/test/unit/generator/pdf/document/graphics/test_image.rb +5 -5
- data/test/unit/generator/pdf/document/test_font.rb +4 -10
- data/test/unit/generator/pdf/document/test_page.rb +13 -11
- data/test/unit/generator/pdf/document/test_parse_color.rb +5 -5
- data/test/unit/generator/test_base.rb +5 -5
- data/test/unit/generator/test_configuration.rb +4 -8
- data/test/unit/generator/test_pdf.rb +3 -2
- data/test/unit/layout/test_base.rb +16 -22
- data/test/unit/layout/test_format.rb +66 -86
- data/test/unit/layout/test_legacy_schema.rb +587 -0
- data/test/unit/layout/test_version.rb +20 -20
- data/test/unit/report/test_base.rb +32 -45
- data/test/unit/report/test_internal.rb +43 -99
- data/test/unit/test_config.rb +4 -2
- data/test/unit/test_layout.rb +1 -1
- metadata +20 -48
- data/examples/list_events/list_events_0_7_7.tlf +0 -1
- data/examples/list_events/list_events_0_8.tlf +0 -1
- data/lib/thinreports/core/events.rb +0 -69
- data/lib/thinreports/core/format/builder.rb +0 -62
- data/lib/thinreports/core/shape/list/configuration.rb +0 -54
- data/lib/thinreports/core/shape/list/events.rb +0 -47
- data/lib/thinreports/core/shape/list/store.rb +0 -33
- data/lib/thinreports/generator/pdf/configuration.rb +0 -33
- data/lib/thinreports/generator/pdf/document/parse_svg.rb +0 -104
- data/lib/thinreports/layout/configuration.rb +0 -29
- data/test/data/layout_block.tlf +0 -1
- data/test/data/layout_list.tlf +0 -1
- data/test/data/layout_list_noheader.tlf +0 -1
- data/test/data/layout_text1.tlf +0 -1
- data/test/data/layout_text2.tlf +0 -1
- data/test/unit/core/format/test_builder.rb +0 -109
- data/test/unit/core/shape/basic/test_basic_format.rb +0 -30
- data/test/unit/core/shape/image_block/test_format.rb +0 -58
- data/test/unit/core/shape/list/test_configuration.rb +0 -69
- data/test/unit/core/shape/list/test_events.rb +0 -36
- data/test/unit/core/shape/list/test_store.rb +0 -41
- data/test/unit/core/test_events.rb +0 -93
- data/test/unit/generator/pdf/document/test_draw_shape.rb +0 -44
- data/test/unit/generator/pdf/test_configuration.rb +0 -31
- data/test/unit/layout/test_configuration.rb +0 -21
- data/test/unit/report/test_events.rb +0 -22
@@ -4,6 +4,11 @@ module Thinreports
|
|
4
4
|
module Generator
|
5
5
|
|
6
6
|
module PDF::Graphics
|
7
|
+
STROKE_DASH = {
|
8
|
+
dashed: [2, 2],
|
9
|
+
dotted: [1, 2]
|
10
|
+
}
|
11
|
+
|
7
12
|
# @param [Numeric, String] x1
|
8
13
|
# @param [Numeric, String] y1
|
9
14
|
# @param [Numeric, String] x2
|
@@ -11,7 +16,7 @@ module Thinreports
|
|
11
16
|
# @param [Hash] attrs ({})
|
12
17
|
# @option attrs [String] :stroke
|
13
18
|
# @option attrs [Numeric, String] :stroke_width
|
14
|
-
# @option attrs [
|
19
|
+
# @option attrs ["solid", "dashed", "dotted"] :stroke_type
|
15
20
|
def line(x1, y1, x2, y2, attrs = {})
|
16
21
|
with_graphic_styles(attrs) do
|
17
22
|
pdf.line(pos(x1, y1), pos(x2, y2))
|
@@ -26,7 +31,7 @@ module Thinreports
|
|
26
31
|
# @option attrs [Integer, String] :radius
|
27
32
|
# @option attrs [String] :stroke
|
28
33
|
# @option attrs [Numeric, String] :stroke_width
|
29
|
-
# @option attrs [
|
34
|
+
# @option attrs ["solid", "dashed", "dotted"] :stroke_type
|
30
35
|
# @option attrs [String] :fill
|
31
36
|
def rect(x, y, w, h, attrs = {})
|
32
37
|
w, h = s2f(w, h)
|
@@ -49,6 +54,7 @@ module Thinreports
|
|
49
54
|
# @option attrs [String] :stroke
|
50
55
|
# @option attrs [Numeric, String] :stroke_width
|
51
56
|
# @option attrs [Array<Integer, String>] :stroke_dash
|
57
|
+
# @option attrs ["solid", "dashed", "dotted"] :stroke_type
|
52
58
|
# @option attrs [String] :fill
|
53
59
|
def ellipse(x, y, rx, ry, attrs = {})
|
54
60
|
rx, ry = s2f(rx, ry)
|
@@ -58,8 +64,6 @@ module Thinreports
|
|
58
64
|
end
|
59
65
|
end
|
60
66
|
|
61
|
-
private
|
62
|
-
|
63
67
|
# @param [Hash] attrs
|
64
68
|
def with_graphic_styles(attrs, &block)
|
65
69
|
stroke = build_stroke_styles(attrs)
|
@@ -108,22 +112,23 @@ module Thinreports
|
|
108
112
|
def build_stroke_styles(styles)
|
109
113
|
color = styles[:stroke]
|
110
114
|
width = styles[:stroke_width]
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
115
|
+
return nil unless color && color != 'none'
|
116
|
+
return nil unless width && width != 0
|
117
|
+
|
118
|
+
{
|
119
|
+
color: parse_color(color),
|
120
|
+
width: s2f(width),
|
121
|
+
dash: STROKE_DASH[styles[:stroke_type].to_sym]
|
122
|
+
}
|
117
123
|
end
|
118
124
|
|
119
125
|
# @param [Hash] styles
|
120
126
|
# @return [Hash, nil]
|
121
127
|
def build_fill_styles(styles)
|
122
128
|
color = styles[:fill]
|
129
|
+
return nil unless color && color != 'none'
|
123
130
|
|
124
|
-
|
125
|
-
{color: parse_color(color)}
|
126
|
-
end
|
131
|
+
{ color: parse_color(color) }
|
127
132
|
end
|
128
133
|
|
129
134
|
end
|
@@ -30,7 +30,7 @@ module Thinreports
|
|
30
30
|
image(image_path, x, y, w, h)
|
31
31
|
end
|
32
32
|
|
33
|
-
# @param
|
33
|
+
# @param [String, IO] file
|
34
34
|
# @param [Numeric, Strng] x
|
35
35
|
# @param [Numeric, Strng] y
|
36
36
|
# @param [Numeric, Strng] w
|
@@ -39,13 +39,16 @@ module Thinreports
|
|
39
39
|
# @option options [:left, :center, :right] :position_x (:left)
|
40
40
|
# @option options [:top, :center, :bottom] :position_y (:top)
|
41
41
|
def image_box(file, x, y, w, h, options = {})
|
42
|
-
|
42
|
+
image_data_or_path = normalize_image_from_file(file)
|
43
43
|
|
44
44
|
w, h = s2f(w, h)
|
45
45
|
pdf.bounding_box(pos(x, y), width: w, height: h) do
|
46
|
-
pdf.image(
|
47
|
-
|
48
|
-
|
46
|
+
pdf.image(
|
47
|
+
image_data_or_path,
|
48
|
+
position: options[:position_x] || :left,
|
49
|
+
vposition: options[:position_y] || :top,
|
50
|
+
auto_fit: [w, h]
|
51
|
+
)
|
49
52
|
end
|
50
53
|
end
|
51
54
|
|
@@ -69,13 +72,16 @@ module Thinreports
|
|
69
72
|
create_temp_imagefile(image_id, image_data)
|
70
73
|
end
|
71
74
|
|
72
|
-
def normalize_image_from_file(
|
75
|
+
def normalize_image_from_file(file)
|
76
|
+
return file unless file.is_a?(String)
|
77
|
+
|
78
|
+
filename = file
|
73
79
|
extname = File.extname(filename)
|
74
80
|
|
75
81
|
return filename unless png_conversion_enabled?
|
76
82
|
return filename unless extname.downcase == '.png' || extname.empty?
|
77
83
|
|
78
|
-
return temp_image_registry[filename] if temp_image_registry.key?(filename)
|
84
|
+
return temp_image_registry[filename].path if temp_image_registry.key?(filename)
|
79
85
|
|
80
86
|
png_normalizer = PNGNormalizer.load_file(filename)
|
81
87
|
|
@@ -101,17 +107,16 @@ module Thinreports
|
|
101
107
|
|
102
108
|
# @param [String] image_id
|
103
109
|
# @param [String] image_data
|
104
|
-
# @return [String] imagefile
|
110
|
+
# @return [String] Path to imagefile
|
105
111
|
def create_temp_imagefile(image_id, image_data)
|
106
112
|
temp_image_registry[image_id] ||= begin
|
107
113
|
file = Tempfile.new('temp-image')
|
108
114
|
file.binmode
|
109
115
|
file.write(image_data)
|
110
|
-
file.
|
111
|
-
|
112
|
-
file.close
|
116
|
+
file.open
|
117
|
+
file
|
113
118
|
end
|
114
|
-
temp_image_registry[image_id]
|
119
|
+
temp_image_registry[image_id].path
|
115
120
|
end
|
116
121
|
|
117
122
|
def png_conversion_enabled?
|
@@ -48,7 +48,7 @@ module Thinreports
|
|
48
48
|
# @param [Thinreports::Layout::Format] format
|
49
49
|
def create_format_stamp(format)
|
50
50
|
create_stamp(format.identifier.to_s) do
|
51
|
-
|
51
|
+
draw_template_items(format.attributes['items'])
|
52
52
|
end
|
53
53
|
format_stamp_registry << format.identifier
|
54
54
|
end
|
@@ -43,7 +43,11 @@ module Thinreports
|
|
43
43
|
# @param [Thinreports::Core::Shape::List::SectionInterface] section
|
44
44
|
def draw_section(section)
|
45
45
|
internal = section.internal
|
46
|
-
|
46
|
+
|
47
|
+
base_top = @format.section_base_position_top(internal.section_name)
|
48
|
+
position = [internal.relative_left, base_top + internal.relative_top]
|
49
|
+
|
50
|
+
drawer(internal).draw(section, position)
|
47
51
|
end
|
48
52
|
|
49
53
|
# @param [Thinreports::Core::Shape::List::SectionInternal] section
|
@@ -26,10 +26,7 @@ module Thinreports
|
|
26
26
|
id = @format.identifier.to_s
|
27
27
|
|
28
28
|
unless @stamp_created
|
29
|
-
@pdf.create_stamp(id)
|
30
|
-
@pdf.parse_svg('<svg xmlns:xlink="http://www.w3.org/1999/xlink">' +
|
31
|
-
"#{@format.layout}</svg>", '/svg')
|
32
|
-
end
|
29
|
+
@pdf.create_stamp(id) { @pdf.draw_template_items(@format.attributes['items']) }
|
33
30
|
@stamp_created = true
|
34
31
|
end
|
35
32
|
pdf_stamp(id)
|
@@ -20,7 +20,7 @@ module Thinreports
|
|
20
20
|
|
21
21
|
shape = shape.internal
|
22
22
|
|
23
|
-
if shape.type_of?(
|
23
|
+
if shape.type_of?(Core::Shape::PageNumber::TYPE_NAME)
|
24
24
|
# Do not draw pageno if is not for Report
|
25
25
|
draw_pageno_shape(shape, page) if page.count? && shape.for_report?
|
26
26
|
else
|
@@ -33,11 +33,11 @@ module Thinreports
|
|
33
33
|
|
34
34
|
def draw_shape(shape)
|
35
35
|
case
|
36
|
-
when shape.type_of?(
|
36
|
+
when shape.type_of?(Core::Shape::TextBlock::TYPE_NAME)
|
37
37
|
draw_tblock_shape(shape)
|
38
|
-
when shape.type_of?(
|
38
|
+
when shape.type_of?(Core::Shape::List::TYPE_NAME)
|
39
39
|
draw_list_shape(shape)
|
40
|
-
when shape.type_of?(
|
40
|
+
when shape.type_of?(Core::Shape::ImageBlock::TYPE_NAME)
|
41
41
|
draw_iblock_shape(shape)
|
42
42
|
else
|
43
43
|
id = shape.identifier
|
@@ -72,11 +72,11 @@ module Thinreports
|
|
72
72
|
# @param [Thinreports::Core::Shape::Base::Internal] shape
|
73
73
|
def create_basic_shape_stamp(shape)
|
74
74
|
case
|
75
|
-
when shape.type_of?(
|
76
|
-
when shape.type_of?(
|
77
|
-
when shape.type_of?(
|
78
|
-
when shape.type_of?(
|
79
|
-
when shape.type_of?(
|
75
|
+
when shape.type_of?('text') then create_text_stamp(shape)
|
76
|
+
when shape.type_of?('image') then create_image_stamp(shape)
|
77
|
+
when shape.type_of?('ellipse') then create_ellipse_stamp(shape)
|
78
|
+
when shape.type_of?('rect') then create_rect_stamp(shape)
|
79
|
+
when shape.type_of?('line') then create_line_stamp(shape)
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
data/lib/thinreports/layout.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
3
|
module Thinreports
|
4
|
-
|
4
|
+
|
5
5
|
module Layout
|
6
6
|
# @see Thinreports::Layout::Base#initialize
|
7
7
|
def self.new(filename, options = {})
|
8
8
|
Base.new(filename, options)
|
9
9
|
end
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
end
|
13
13
|
|
14
14
|
require 'thinreports/layout/version'
|
15
15
|
require 'thinreports/layout/base'
|
16
16
|
require 'thinreports/layout/format'
|
17
|
-
require 'thinreports/layout/
|
17
|
+
require 'thinreports/layout/legacy_schema'
|
@@ -44,22 +44,6 @@ module Thinreports
|
|
44
44
|
def default?
|
45
45
|
@id.nil?
|
46
46
|
end
|
47
|
-
|
48
|
-
# @yield [config]
|
49
|
-
# @yieldparam [List::Configuration] config
|
50
|
-
# @return [List::Configuration]
|
51
|
-
def config(&block)
|
52
|
-
@config ||= Layout::Configuration.new(self)
|
53
|
-
call_block_in(@config, &block)
|
54
|
-
end
|
55
|
-
|
56
|
-
# @param [Thinreports::Report::Base] parent
|
57
|
-
# @param [Hash] options ({})
|
58
|
-
# @option option [Boolean] :count (true)
|
59
|
-
# @return [Page]
|
60
|
-
def new_page(parent, options = {})
|
61
|
-
Report::Page.new(parent, self, options)
|
62
|
-
end
|
63
47
|
end
|
64
48
|
|
65
49
|
end
|
@@ -1,80 +1,61 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
|
3
|
-
require 'digest/sha1'
|
2
|
+
require 'json'
|
4
3
|
|
5
4
|
module Thinreports
|
6
5
|
module Layout
|
7
|
-
|
8
6
|
class Format < Core::Shape::Manager::Format
|
9
7
|
config_reader last_version: %w( version )
|
10
|
-
config_reader report_title: %w(
|
11
|
-
config_reader page_paper_type: %w(
|
12
|
-
page_width: %w(
|
13
|
-
page_height: %w(
|
14
|
-
page_orientation: %w(
|
15
|
-
|
16
|
-
config_checker 'user', user_paper_type: %w( config page paper-type )
|
8
|
+
config_reader report_title: %w( title )
|
9
|
+
config_reader page_paper_type: %w( report paper-type ),
|
10
|
+
page_width: %w( report width ),
|
11
|
+
page_height: %w( report height ),
|
12
|
+
page_orientation: %w( report orientation )
|
17
13
|
|
18
14
|
class << self
|
15
|
+
def build(filename)
|
16
|
+
schema = JSON.parse(read_file(filename))
|
19
17
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
def build_internal(filename, options = {})
|
26
|
-
build_once(filename, options[:force]) do |content, id|
|
27
|
-
raw_format = parse_json(content)
|
28
|
-
|
29
|
-
# Check the compatibility of specified layout file.
|
30
|
-
unless Thinreports::Layout::Version.compatible?(raw_format['version'])
|
31
|
-
info = [filename, raw_format['version'],
|
32
|
-
Thinreports::Layout::Version.inspect_required_rules]
|
33
|
-
raise Thinreports::Errors::IncompatibleLayoutFormat.new(*info)
|
34
|
-
end
|
35
|
-
|
36
|
-
compact_format!(raw_format)
|
18
|
+
unless Layout::Version.compatible?(schema['version'])
|
19
|
+
raise Errors::IncompatibleLayoutFormat.new(
|
20
|
+
filename, schema['version'], Thinreports::Layout::Version.inspect_required_rules
|
21
|
+
)
|
22
|
+
end
|
37
23
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
43
|
-
clean(f.layout)
|
44
|
-
end
|
24
|
+
if schema['version'] < '0.9.0'
|
25
|
+
warn '[DEPRECATION] Support for the layout file with old format that generated with Editor 0.8 or lower will be dropped in Thinreports 1.1.' \
|
26
|
+
' Please convert to new layout format using Thinreports Editor 0.9 or 1.0.'
|
27
|
+
schema = Layout::LegacySchema.new(schema).upgrade
|
45
28
|
end
|
29
|
+
|
30
|
+
new schema
|
46
31
|
end
|
47
32
|
|
48
|
-
|
49
|
-
|
50
|
-
%w( finger-print state version ).each {|attr| raw_format.delete(attr) }
|
33
|
+
def read_file(filename)
|
34
|
+
File.open(filename, 'r:UTF-8') {|f| f.read }
|
51
35
|
end
|
36
|
+
end
|
52
37
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
38
|
+
def initialize(*)
|
39
|
+
super
|
40
|
+
initialize_items(attributes['items'])
|
41
|
+
end
|
57
42
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
id = Digest::SHA1.hexdigest(content).to_sym
|
62
|
-
built_format_registry[id] ||= block.call(content, id)
|
63
|
-
end
|
64
|
-
end
|
43
|
+
def user_paper_type?
|
44
|
+
page_paper_type == 'user'
|
45
|
+
end
|
65
46
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
47
|
+
private
|
48
|
+
|
49
|
+
def initialize_items(item_schemas)
|
50
|
+
item_schemas.each do |item_schema|
|
51
|
+
id, type = item_schema.values_at 'id', 'type'
|
71
52
|
|
72
|
-
|
73
|
-
|
74
|
-
|
53
|
+
next if id.empty? && type != 'page-number'
|
54
|
+
|
55
|
+
item = Core::Shape::Format(type).new(item_schema)
|
56
|
+
self.shapes[item.id.to_sym] = item
|
75
57
|
end
|
76
58
|
end
|
77
59
|
end
|
78
|
-
|
79
60
|
end
|
80
61
|
end
|
@@ -0,0 +1,354 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'rexml/document'
|
5
|
+
|
6
|
+
module Thinreports
|
7
|
+
module Layout
|
8
|
+
class LegacySchema
|
9
|
+
def initialize(legacy_schema)
|
10
|
+
@legacy_schema = legacy_schema
|
11
|
+
end
|
12
|
+
|
13
|
+
def upgrade
|
14
|
+
config = legacy_schema['config']
|
15
|
+
page_config = config['page']
|
16
|
+
|
17
|
+
{
|
18
|
+
'version' => legacy_schema['version'],
|
19
|
+
'title' => legacy_schema['config']['title'],
|
20
|
+
'report' => {
|
21
|
+
'paper-type' => page_config['paper-type'],
|
22
|
+
'width' => page_config['width'].to_f,
|
23
|
+
'height' => page_config['height'].to_f,
|
24
|
+
'orientation' => page_config['orientation'],
|
25
|
+
'margin' => page_config.values_at('margin-top', 'margin-right', 'margin-bottom', 'margin-left').map(&:to_f)
|
26
|
+
},
|
27
|
+
'items' => build_item_schemas_from_svg(legacy_schema['svg'].dup, '/svg/g', 1)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :legacy_schema
|
32
|
+
|
33
|
+
def build_item_schemas_from_svg(svg, root_xpath, level)
|
34
|
+
item_schemas = extract_item_schemas(svg, level)
|
35
|
+
|
36
|
+
svg_doc = REXML::Document.new(normalize_svg(svg, level))
|
37
|
+
items = []
|
38
|
+
|
39
|
+
svg_doc.elements[root_xpath].each do |item_element|
|
40
|
+
item_schema = item_schemas[item_element.attributes['x-id']] || {}
|
41
|
+
item_attributes = item_element.attributes
|
42
|
+
|
43
|
+
items <<
|
44
|
+
case item_element.attributes['class']
|
45
|
+
when 's-text' then text_item_schema(item_attributes, extract_texts_from(item_element))
|
46
|
+
when 's-image' then image_item_schema(item_attributes)
|
47
|
+
when 's-rect' then rect_item_schema(item_attributes)
|
48
|
+
when 's-ellipse' then ellipse_item_schema(item_attributes)
|
49
|
+
when 's-line' then line_item_schema(item_attributes)
|
50
|
+
when 's-tblock' then text_block_item_schema(item_attributes)
|
51
|
+
when 's-iblock' then image_block_item_schema(item_attributes)
|
52
|
+
when 's-pageno' then page_number_item_schema(item_attributes)
|
53
|
+
when 's-list' then list_item_schema(item_schema)
|
54
|
+
else raise 'Unknown item type'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
items
|
59
|
+
end
|
60
|
+
|
61
|
+
def text_item_schema(attributes, texts)
|
62
|
+
{
|
63
|
+
'id' => attributes['x-id'],
|
64
|
+
'type' => Core::Shape::Text::TYPE_NAME,
|
65
|
+
'x' => attributes['x-left'].to_f,
|
66
|
+
'y' => attributes['x-top'].to_f,
|
67
|
+
'width' => attributes['x-width'].to_f,
|
68
|
+
'height' => attributes['x-height'].to_f,
|
69
|
+
'display' => display(attributes['x-display']),
|
70
|
+
'texts' => texts,
|
71
|
+
'style' => {
|
72
|
+
'font-family' => [ attributes['font-family'] ],
|
73
|
+
'font-size' => attributes['font-size'].to_f,
|
74
|
+
'color' => attributes['fill'],
|
75
|
+
'font-style' => font_style(attributes),
|
76
|
+
'text-align' => text_align(attributes['text-anchor']),
|
77
|
+
'vertical-align' => vertical_align(attributes['x-valign']),
|
78
|
+
'line-height' => line_height(attributes['x-line-height']),
|
79
|
+
'letter-spacing' => letter_spacing(attributes['kerning'])
|
80
|
+
}
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
def rect_item_schema(attributes)
|
85
|
+
{
|
86
|
+
'id' => attributes['x-id'],
|
87
|
+
'type' => 'rect',
|
88
|
+
'x' => attributes['x'].to_f,
|
89
|
+
'y' => attributes['y'].to_f,
|
90
|
+
'width' => attributes['width'].to_f,
|
91
|
+
'height' => attributes['height'].to_f,
|
92
|
+
'display' => display(attributes['x-display']),
|
93
|
+
'style' => {
|
94
|
+
'border-width' => attributes['stroke-width'].to_f,
|
95
|
+
'border-color' => attributes['stroke'],
|
96
|
+
'border-style' => attributes['x-stroke-type'],
|
97
|
+
'fill-color' => attributes['fill']
|
98
|
+
}
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
def line_item_schema(attributes)
|
103
|
+
{
|
104
|
+
'id' => attributes['x-id'],
|
105
|
+
'type' => 'line',
|
106
|
+
'x1' => attributes['x1'].to_f,
|
107
|
+
'y1' => attributes['y1'].to_f,
|
108
|
+
'x2' => attributes['x2'].to_f,
|
109
|
+
'y2' => attributes['y2'].to_f,
|
110
|
+
'display' => display(attributes['x-display']),
|
111
|
+
'style' => {
|
112
|
+
'border-width' => attributes['stroke-width'].to_f,
|
113
|
+
'border-color' => attributes['stroke'],
|
114
|
+
'border-style' => attributes['x-stroke-type']
|
115
|
+
}
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
def ellipse_item_schema(attributes)
|
120
|
+
{
|
121
|
+
'id' => attributes['x-id'],
|
122
|
+
'type' => 'ellipse',
|
123
|
+
'cx' => attributes['cx'].to_f,
|
124
|
+
'cy' => attributes['cy'].to_f,
|
125
|
+
'rx' => attributes['rx'].to_f,
|
126
|
+
'ry' => attributes['ry'].to_f,
|
127
|
+
'display' => display(attributes['x-display']),
|
128
|
+
'style' => {
|
129
|
+
'border-width' => attributes['stroke-width'].to_f,
|
130
|
+
'border-color' => attributes['stroke'],
|
131
|
+
'border-style' => attributes['x-stroke-type'],
|
132
|
+
'fill-color' => attributes['fill']
|
133
|
+
}
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
def image_item_schema(attributes)
|
138
|
+
_, image_type, image_data = attributes['xlink:href'].match(%r|^data:(image/[a-z]+?);base64,(.+)|).to_a
|
139
|
+
|
140
|
+
{
|
141
|
+
'id' => attributes['x-id'],
|
142
|
+
'type' => 'image',
|
143
|
+
'x' => attributes['x'].to_f,
|
144
|
+
'y' => attributes['y'].to_f,
|
145
|
+
'width' => attributes['width'].to_f,
|
146
|
+
'height' => attributes['height'].to_f,
|
147
|
+
'display' => display(attributes['x-display']),
|
148
|
+
'data' => {
|
149
|
+
'mime-type' => image_type,
|
150
|
+
'base64' => image_data
|
151
|
+
}
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
def page_number_item_schema(attributes)
|
156
|
+
{
|
157
|
+
'id' => attributes['x-id'],
|
158
|
+
'type' => Core::Shape::PageNumber::TYPE_NAME,
|
159
|
+
'x' => attributes['x-left'].to_f,
|
160
|
+
'y' => attributes['x-top'].to_f,
|
161
|
+
'width' => attributes['x-width'].to_f,
|
162
|
+
'height' => attributes['x-height'].to_f,
|
163
|
+
'format' => attributes['x-format'],
|
164
|
+
'target' => attributes['x-target'],
|
165
|
+
'display' => display(attributes['x-display']),
|
166
|
+
'style' => {
|
167
|
+
'font-family' => [ attributes['font-family'] ],
|
168
|
+
'font-size' => attributes['font-size'].to_f,
|
169
|
+
'color' => attributes['fill'],
|
170
|
+
'font-style' => font_style(attributes),
|
171
|
+
'text-align' => text_align(attributes['text-anchor']),
|
172
|
+
'overflow' => attributes['x-overflow']
|
173
|
+
}
|
174
|
+
}
|
175
|
+
end
|
176
|
+
|
177
|
+
def image_block_item_schema(attributes)
|
178
|
+
{
|
179
|
+
'id' => attributes['x-id'],
|
180
|
+
'type' => Core::Shape::ImageBlock::TYPE_NAME,
|
181
|
+
'x' => attributes['x-left'].to_f,
|
182
|
+
'y' => attributes['x-top'].to_f,
|
183
|
+
'width' => attributes['x-width'].to_f,
|
184
|
+
'height' => attributes['x-height'].to_f,
|
185
|
+
'display' => display(attributes['x-display']),
|
186
|
+
'style' => {
|
187
|
+
'position-x' => attributes['x-position-x'],
|
188
|
+
'position-y' => image_position_y(attributes['x-position-y'])
|
189
|
+
}
|
190
|
+
}
|
191
|
+
end
|
192
|
+
|
193
|
+
def text_block_item_schema(attributes)
|
194
|
+
text_format = {
|
195
|
+
'base' => attributes['x-format-base'],
|
196
|
+
'type' => attributes['x-format-type']
|
197
|
+
}
|
198
|
+
case text_format['type']
|
199
|
+
when 'datetime'
|
200
|
+
text_format['datetime'] = {
|
201
|
+
'format' => attributes['x-format-datetime-format']
|
202
|
+
}
|
203
|
+
when 'number'
|
204
|
+
text_format['number'] = {
|
205
|
+
'delimiter' => attributes['x-format-number-delimiter'],
|
206
|
+
'precision' => attributes['x-format-number-precision'].to_i
|
207
|
+
}
|
208
|
+
when 'padding'
|
209
|
+
text_format['padding'] = {
|
210
|
+
'length' => attributes['x-format-padding-length'].to_i,
|
211
|
+
'char' => attributes['x-format-padding-char'],
|
212
|
+
'direction' => attributes['x-format-padding-direction']
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
{
|
217
|
+
'id' => attributes['x-id'],
|
218
|
+
'type' => Core::Shape::TextBlock::TYPE_NAME,
|
219
|
+
'x' => attributes['x-left'].to_f,
|
220
|
+
'y' => attributes['x-top'].to_f,
|
221
|
+
'width' => attributes['x-width'].to_f,
|
222
|
+
'height' => attributes['x-height'].to_f,
|
223
|
+
'display' => display(attributes['x-display']),
|
224
|
+
'value' => attributes['x-value'],
|
225
|
+
'multiple-line' => attributes['x-multiple'] == 'true',
|
226
|
+
'format' => text_format,
|
227
|
+
'style' => {
|
228
|
+
'font-family' => [ attributes['font-family'] ],
|
229
|
+
'font-size' => attributes['font-size'].to_f,
|
230
|
+
'color' => attributes['fill'],
|
231
|
+
'font-style' => font_style(attributes),
|
232
|
+
'text-align' => text_align(attributes['text-anchor']),
|
233
|
+
'vertical-align' => vertical_align(attributes['x-valign']),
|
234
|
+
'line-height' => line_height(attributes['x-line-height']),
|
235
|
+
'letter-spacing' => letter_spacing(attributes['kerning']),
|
236
|
+
'overflow' => attributes['x-overflow'],
|
237
|
+
'word-wrap' => attributes['x-word-wrap']
|
238
|
+
}
|
239
|
+
}
|
240
|
+
end
|
241
|
+
|
242
|
+
def list_item_schema(legacy_schema)
|
243
|
+
{
|
244
|
+
'id' => legacy_schema['id'],
|
245
|
+
'type' => Core::Shape::List::TYPE_NAME,
|
246
|
+
'content-height' => legacy_schema['content-height'].to_f,
|
247
|
+
'auto-page-break' => legacy_schema['page-break'] == 'true',
|
248
|
+
'display' => display(legacy_schema['display']),
|
249
|
+
'header' => list_section_schema('header', legacy_schema),
|
250
|
+
'detail' => list_section_schema('detail', legacy_schema),
|
251
|
+
'page-footer' => list_section_schema('page-footer', legacy_schema),
|
252
|
+
'footer' => list_section_schema('footer', legacy_schema)
|
253
|
+
}
|
254
|
+
end
|
255
|
+
|
256
|
+
def list_section_schema(section_name, legacy_list_schema)
|
257
|
+
legacy_section_schema = legacy_list_schema[section_name]
|
258
|
+
|
259
|
+
section_svg = %(<svg xmlns:xlink="http://www.w3.org/1999/xlink">#{legacy_section_schema['svg']['content']}</svg>)
|
260
|
+
|
261
|
+
section_schema = {
|
262
|
+
'height' => legacy_section_schema['height'].to_f,
|
263
|
+
'translate' => {
|
264
|
+
'x' => legacy_section_schema['translate']['x'].to_f,
|
265
|
+
'y' => legacy_section_schema['translate']['y'].to_f
|
266
|
+
},
|
267
|
+
'items' => build_item_schemas_from_svg(section_svg, '/svg', 2)
|
268
|
+
}
|
269
|
+
|
270
|
+
unless section_name == 'detail'
|
271
|
+
section_schema['enabled'] = legacy_list_schema["#{section_name}-enabled"] == 'true'
|
272
|
+
end
|
273
|
+
section_schema
|
274
|
+
end
|
275
|
+
|
276
|
+
def extract_texts_from(text_item_element)
|
277
|
+
[].tap do |texts|
|
278
|
+
text_item_element.each_element('text') { |e| texts << e.text }
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def image_position_y(legacy_position_y)
|
283
|
+
case legacy_position_y
|
284
|
+
when 'top' then 'top'
|
285
|
+
when 'center' then 'middle'
|
286
|
+
when 'bottom' then 'bottom'
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def display(legacy_display)
|
291
|
+
legacy_display == 'true'
|
292
|
+
end
|
293
|
+
|
294
|
+
def font_style(attributes)
|
295
|
+
style = []
|
296
|
+
style << 'bold' if attributes['font-weight'] == 'bold'
|
297
|
+
style << 'italic' if attributes['font-style'] == 'italic'
|
298
|
+
style << 'underline' if attributes['text-decoration'].include?('underline')
|
299
|
+
style << 'linethrough' if attributes['text-decoration'].include?('line-through')
|
300
|
+
style
|
301
|
+
end
|
302
|
+
|
303
|
+
def text_align(legacy_text_align)
|
304
|
+
case legacy_text_align
|
305
|
+
when 'start' then 'left'
|
306
|
+
when 'middle' then 'center'
|
307
|
+
when 'end' then 'right'
|
308
|
+
else 'left'
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def vertical_align(legacy_vertical_align)
|
313
|
+
return nil unless legacy_vertical_align
|
314
|
+
|
315
|
+
case legacy_vertical_align
|
316
|
+
when 'top' then 'top'
|
317
|
+
when 'center' then 'middle'
|
318
|
+
when 'bottom' then 'bottom'
|
319
|
+
else 'top'
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
def line_height(legacy_line_height)
|
324
|
+
legacy_line_height == '' ? '' : legacy_line_height.to_f
|
325
|
+
end
|
326
|
+
|
327
|
+
def letter_spacing(legacy_letter_spacing)
|
328
|
+
case legacy_letter_spacing
|
329
|
+
when 'auto', '' then ''
|
330
|
+
else legacy_letter_spacing.to_f
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
def extract_item_schemas(svg, level)
|
335
|
+
items = {}
|
336
|
+
svg.scan(/<!--#{level_symbol(level)}SHAPE(.*?)SHAPE#{level_symbol(level)}-->/) do |(item_schema_json)|
|
337
|
+
item_schema = JSON.parse(item_schema_json)
|
338
|
+
items[item_schema['id']] = item_schema
|
339
|
+
end
|
340
|
+
items
|
341
|
+
end
|
342
|
+
|
343
|
+
def normalize_svg(svg, level)
|
344
|
+
svg.gsub!(/<!--#{level_symbol(level)}SHAPE.*?SHAPE#{level_symbol(level)}-->/, '')
|
345
|
+
svg.gsub!(/<!--#{level_symbol(level)}LAYOUT(.*?)LAYOUT#{level_symbol(level)}-->/) { $1 }
|
346
|
+
svg
|
347
|
+
end
|
348
|
+
|
349
|
+
def level_symbol(level)
|
350
|
+
'-' * (level - 1)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|