idml 0.8.9 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +5 -3
- data/TODO.pdf/101-paragraph-rules-borders.md +36 -39
- data/TODO.pdf/104-table-schema-completion.md +16 -6
- data/TODO.pdf/116-justification-last-line.md +31 -0
- data/TODO.pdf/117-endnote-support.md +45 -0
- data/TODO.pdf/13-cjk-text-layout.md +20 -4
- data/lib/idml/elements/character_style_range.rb +2 -0
- data/lib/idml/elements/endnote.rb +22 -0
- data/lib/idml/elements/endnote_range.rb +21 -0
- data/lib/idml/elements/paragraph_style_range.rb +2 -0
- data/lib/idml/elements/properties.rb +16 -3
- data/lib/idml/elements/story_inner.rb +5 -0
- data/lib/idml/elements/table.rb +369 -1
- data/lib/idml/elements/typed_value.rb +20 -0
- data/lib/idml/elements.rb +3 -0
- data/lib/idml/render/footnote.rb +10 -2
- data/lib/idml/render/paragraph_rules.rb +125 -0
- data/lib/idml/render/renderers/text_frame_renderer.rb +88 -11
- data/lib/idml/render/style_resolver.rb +92 -2
- data/lib/idml/text_engine/justifier.rb +16 -5
- data/lib/idml/text_engine/line_breaker.rb +11 -1
- data/lib/idml/text_engine/measurement.rb +40 -0
- data/lib/idml/text_engine.rb +2 -1
- data/lib/idml/version.rb +1 -1
- metadata +8 -2
|
@@ -25,6 +25,7 @@ module Idml
|
|
|
25
25
|
# FontMetrics is available (no shaper → no wrap → rough output).
|
|
26
26
|
class TextFrameRenderer
|
|
27
27
|
DEFAULT_SIZE = 12.0
|
|
28
|
+
RUBY_SIZE_FACTOR = 0.5
|
|
28
29
|
|
|
29
30
|
def self.render(canvas, context)
|
|
30
31
|
frame = context.item
|
|
@@ -445,6 +446,12 @@ module Idml
|
|
|
445
446
|
|
|
446
447
|
emit_paragraph_top_rule(canvas, paragraph, context, cursor_y,
|
|
447
448
|
frame_left, frame_right, first_paragraph)
|
|
449
|
+
block_top, block_bottom = paragraph_block_extent(
|
|
450
|
+
paragraph, font, wrap_width, cursor_y, bottom_limit
|
|
451
|
+
)
|
|
452
|
+
ParagraphRules.emit_shading(canvas, paragraph, context,
|
|
453
|
+
block_top, block_bottom,
|
|
454
|
+
frame_left, frame_right)
|
|
448
455
|
drop_cap = emit_drop_cap(canvas, paragraph, context, frame_left,
|
|
449
456
|
cursor_y, first_paragraph)
|
|
450
457
|
|
|
@@ -463,10 +470,28 @@ module Idml
|
|
|
463
470
|
cursor_y, frame_left,
|
|
464
471
|
frame_right, para_attrs,
|
|
465
472
|
remaining_runs)
|
|
473
|
+
ParagraphRules.emit_border(canvas, paragraph, context,
|
|
474
|
+
block_top, block_bottom,
|
|
475
|
+
frame_left, frame_right)
|
|
466
476
|
[remaining_runs, cursor_y, char_cursor]
|
|
467
477
|
end
|
|
468
478
|
private_class_method :render_runs_for_paragraph
|
|
469
479
|
|
|
480
|
+
# The paragraph's block extent: top at the current cursor,
|
|
481
|
+
# bottom at the pre-measured height clamped to the frame's
|
|
482
|
+
# bottom limit. Shading and border both use this one rect so
|
|
483
|
+
# they agree even when the paragraph splits across frames
|
|
484
|
+
# (the split case shades only the rendered part).
|
|
485
|
+
def self.paragraph_block_extent(paragraph, font, wrap_width,
|
|
486
|
+
cursor_y, bottom_limit)
|
|
487
|
+
height = TextEngine::Measurement.paragraph_height(
|
|
488
|
+
paragraph, font, wrap_width
|
|
489
|
+
)
|
|
490
|
+
block_bottom = [cursor_y - height, bottom_limit].max
|
|
491
|
+
[cursor_y, block_bottom]
|
|
492
|
+
end
|
|
493
|
+
private_class_method :paragraph_block_extent
|
|
494
|
+
|
|
470
495
|
def self.iterate_paragraph_runs(canvas, paragraph, runs, context,
|
|
471
496
|
layout_frame, font, wrap_width,
|
|
472
497
|
cursor_y, bottom_limit, char_cursor,
|
|
@@ -486,7 +511,8 @@ module Idml
|
|
|
486
511
|
canvas, run, paragraph, context, layout_frame, font,
|
|
487
512
|
run_wrap, cursor_y, space_before,
|
|
488
513
|
para_attrs[:first_line_indent], para_attrs[:left_indent],
|
|
489
|
-
char_cursor, bottom_limit
|
|
514
|
+
char_cursor, bottom_limit,
|
|
515
|
+
paragraph_last: run.equal?(runs.last)
|
|
490
516
|
)
|
|
491
517
|
char_cursor += positioned.length
|
|
492
518
|
cursor_y = next_y
|
|
@@ -687,7 +713,7 @@ module Idml
|
|
|
687
713
|
def self.layout_run(canvas, run, paragraph, context, layout_frame,
|
|
688
714
|
font, wrap_width, cursor_y, space_before,
|
|
689
715
|
first_line_indent, left_indent,
|
|
690
|
-
char_cursor, bottom_limit)
|
|
716
|
+
char_cursor, bottom_limit, paragraph_last: false)
|
|
691
717
|
size = run.point_size || DEFAULT_SIZE
|
|
692
718
|
glyphs = TextEngine::Shaper.shape(
|
|
693
719
|
text: run.text, font: font, size: size,
|
|
@@ -695,12 +721,7 @@ module Idml
|
|
|
695
721
|
lines = TextEngine::LineBreaker.break(
|
|
696
722
|
glyphs: glyphs, frame_width: wrap_width,
|
|
697
723
|
)
|
|
698
|
-
lines
|
|
699
|
-
TextEngine::Justifier.justify(
|
|
700
|
-
line: line, frame_width: wrap_width,
|
|
701
|
-
alignment: paragraph.alignment || :left
|
|
702
|
-
)
|
|
703
|
-
end
|
|
724
|
+
justify_lines(lines, paragraph, wrap_width, paragraph_last)
|
|
704
725
|
leading = leading_for(paragraph, size)
|
|
705
726
|
|
|
706
727
|
positioned, next_y = TextEngine::VerticalLayout.layout_block(
|
|
@@ -714,15 +735,41 @@ module Idml
|
|
|
714
735
|
left_indent: left_indent,
|
|
715
736
|
)
|
|
716
737
|
|
|
717
|
-
positioned
|
|
738
|
+
emit_run_lines(canvas, positioned, run, context, size, font,
|
|
739
|
+
bottom_limit, char_cursor)
|
|
740
|
+
[positioned, next_y]
|
|
741
|
+
end
|
|
742
|
+
private_class_method :layout_run
|
|
743
|
+
|
|
744
|
+
# Justifies the run's lines; only the final line of the
|
|
745
|
+
# paragraph's final run stays ragged under full
|
|
746
|
+
# justification.
|
|
747
|
+
def self.justify_lines(lines, paragraph, wrap_width,
|
|
748
|
+
paragraph_last)
|
|
749
|
+
lines.each_with_index do |line, index|
|
|
750
|
+
TextEngine::Justifier.justify(
|
|
751
|
+
line: line, frame_width: wrap_width,
|
|
752
|
+
alignment: paragraph.alignment || :left,
|
|
753
|
+
last_line: paragraph_last && index == lines.length - 1
|
|
754
|
+
)
|
|
755
|
+
end
|
|
756
|
+
end
|
|
757
|
+
private_class_method :justify_lines
|
|
758
|
+
|
|
759
|
+
# Emits the run's positioned lines (clipped at the bottom
|
|
760
|
+
# limit), recording positions and annotating the first line
|
|
761
|
+
# with the run's ruby when present.
|
|
762
|
+
def self.emit_run_lines(canvas, positioned, run, context, size,
|
|
763
|
+
font, bottom_limit, char_cursor)
|
|
764
|
+
positioned.each_with_index do |line, index|
|
|
718
765
|
break if line.y < bottom_limit
|
|
719
766
|
|
|
720
767
|
emit_line(canvas, line, run, context, size)
|
|
721
768
|
record_position(context, line, size, char_cursor)
|
|
769
|
+
emit_ruby(canvas, run, line, size, font, context) if index.zero?
|
|
722
770
|
end
|
|
723
|
-
[positioned, next_y]
|
|
724
771
|
end
|
|
725
|
-
private_class_method :
|
|
772
|
+
private_class_method :emit_run_lines
|
|
726
773
|
|
|
727
774
|
# Emits one positioned line: applies character-level styling
|
|
728
775
|
# (fill color + tint, capitalization, position, tracking,
|
|
@@ -755,6 +802,36 @@ module Idml
|
|
|
755
802
|
end
|
|
756
803
|
private_class_method :emit_line
|
|
757
804
|
|
|
805
|
+
# Emits the run's ruby (phonetic annotation) above the first
|
|
806
|
+
# line of the annotated run, centered over the line's width.
|
|
807
|
+
# RubyFontSize defaults to half the base size; RubyPosition
|
|
808
|
+
# values containing "Below" place it under the base text.
|
|
809
|
+
# Approximation: IDML attaches ruby to a character range;
|
|
810
|
+
# we annotate the whole first line.
|
|
811
|
+
def self.emit_ruby(canvas, run, line, base_size, font, context)
|
|
812
|
+
ruby_text = run.ruby_string
|
|
813
|
+
return if ruby_text.nil? || ruby_text.empty?
|
|
814
|
+
|
|
815
|
+
ruby_size = run.ruby_font_size || (base_size * RUBY_SIZE_FACTOR)
|
|
816
|
+
glyphs = TextEngine::Shaper.shape(text: ruby_text, font: font,
|
|
817
|
+
size: ruby_size)
|
|
818
|
+
ruby_width = glyphs.sum(&:width)
|
|
819
|
+
x = line.x + ((line.width - ruby_width) / 2)
|
|
820
|
+
y = ruby_y(run, line, base_size, ruby_size)
|
|
821
|
+
canvas.text(ruby_text, at: [x, y],
|
|
822
|
+
font: font_for_run(run, context), size: ruby_size)
|
|
823
|
+
end
|
|
824
|
+
private_class_method :emit_ruby
|
|
825
|
+
|
|
826
|
+
def self.ruby_y(run, line, base_size, ruby_size)
|
|
827
|
+
if run.ruby_position.to_s.include?("Below")
|
|
828
|
+
line.y - (base_size * 0.25) - ruby_size
|
|
829
|
+
else
|
|
830
|
+
line.y + (base_size * 0.45) + ruby_size
|
|
831
|
+
end
|
|
832
|
+
end
|
|
833
|
+
private_class_method :ruby_y
|
|
834
|
+
|
|
758
835
|
def self.leading_for(paragraph, size)
|
|
759
836
|
TextEngine::VerticalLayout.leading_for(paragraph.auto_leading, size)
|
|
760
837
|
end
|
|
@@ -43,6 +43,11 @@ module Idml
|
|
|
43
43
|
:baseline_shift,
|
|
44
44
|
:footnote_number,
|
|
45
45
|
:footnote_paragraphs,
|
|
46
|
+
# Ruby (phonetic annotation) — from the CSR's Ruby*
|
|
47
|
+
# attributes. Present only on runs with a RubyString.
|
|
48
|
+
:ruby_string,
|
|
49
|
+
:ruby_font_size,
|
|
50
|
+
:ruby_position,
|
|
46
51
|
keyword_init: true,
|
|
47
52
|
)
|
|
48
53
|
|
|
@@ -82,6 +87,28 @@ module Idml
|
|
|
82
87
|
:rule_below_left_indent,
|
|
83
88
|
:rule_below_right_indent,
|
|
84
89
|
:rule_below_width,
|
|
90
|
+
# ParagraphShading — fill rect behind the paragraph block.
|
|
91
|
+
# Color comes from Properties > ParagraphShadingColor.
|
|
92
|
+
:paragraph_shading_on,
|
|
93
|
+
:paragraph_shading_color,
|
|
94
|
+
:paragraph_shading_tint,
|
|
95
|
+
:paragraph_shading_width,
|
|
96
|
+
:paragraph_shading_left_offset,
|
|
97
|
+
:paragraph_shading_right_offset,
|
|
98
|
+
:paragraph_shading_top_offset,
|
|
99
|
+
:paragraph_shading_bottom_offset,
|
|
100
|
+
# ParagraphBorder — per-side strokes around the paragraph.
|
|
101
|
+
:paragraph_border_on,
|
|
102
|
+
:paragraph_border_color,
|
|
103
|
+
:paragraph_border_tint,
|
|
104
|
+
:paragraph_border_top_line_weight,
|
|
105
|
+
:paragraph_border_left_line_weight,
|
|
106
|
+
:paragraph_border_right_line_weight,
|
|
107
|
+
:paragraph_border_bottom_line_weight,
|
|
108
|
+
:paragraph_border_top_offset,
|
|
109
|
+
:paragraph_border_left_offset,
|
|
110
|
+
:paragraph_border_right_offset,
|
|
111
|
+
:paragraph_border_bottom_offset,
|
|
85
112
|
keyword_init: true,
|
|
86
113
|
)
|
|
87
114
|
|
|
@@ -171,7 +198,8 @@ module Idml
|
|
|
171
198
|
rule_above: resolve_attr(style_lookup, psr, :rule_above),
|
|
172
199
|
rule_above_line_weight: resolve_attr(style_lookup, psr,
|
|
173
200
|
:rule_above_line_weight),
|
|
174
|
-
rule_above_color:
|
|
201
|
+
rule_above_color: properties_color(psr, :rule_above_color) ||
|
|
202
|
+
resolve_attr(style_lookup, psr, :stroke_color),
|
|
175
203
|
rule_above_tint: resolve_attr(style_lookup, psr,
|
|
176
204
|
:rule_above_tint),
|
|
177
205
|
rule_above_offset: resolve_attr(style_lookup, psr,
|
|
@@ -185,7 +213,8 @@ module Idml
|
|
|
185
213
|
rule_below: resolve_attr(style_lookup, psr, :rule_below),
|
|
186
214
|
rule_below_line_weight: resolve_attr(style_lookup, psr,
|
|
187
215
|
:rule_below_line_weight),
|
|
188
|
-
rule_below_color:
|
|
216
|
+
rule_below_color: properties_color(psr, :rule_below_color) ||
|
|
217
|
+
resolve_attr(style_lookup, psr, :stroke_color),
|
|
189
218
|
rule_below_tint: resolve_attr(style_lookup, psr,
|
|
190
219
|
:rule_below_tint),
|
|
191
220
|
rule_below_offset: resolve_attr(style_lookup, psr,
|
|
@@ -196,12 +225,70 @@ module Idml
|
|
|
196
225
|
:rule_below_right_indent),
|
|
197
226
|
rule_below_width: resolve_attr(style_lookup, psr,
|
|
198
227
|
:rule_below_width),
|
|
228
|
+
paragraph_shading_on: resolve_attr(style_lookup, psr,
|
|
229
|
+
:paragraph_shading_on),
|
|
230
|
+
paragraph_shading_color: properties_color(psr,
|
|
231
|
+
:paragraph_shading_color),
|
|
232
|
+
paragraph_shading_tint: resolve_attr(style_lookup, psr,
|
|
233
|
+
:paragraph_shading_tint),
|
|
234
|
+
paragraph_shading_width: resolve_attr(style_lookup, psr,
|
|
235
|
+
:paragraph_shading_width),
|
|
236
|
+
paragraph_shading_left_offset: resolve_attr(
|
|
237
|
+
style_lookup, psr, :paragraph_shading_left_offset
|
|
238
|
+
),
|
|
239
|
+
paragraph_shading_right_offset: resolve_attr(
|
|
240
|
+
style_lookup, psr, :paragraph_shading_right_offset
|
|
241
|
+
),
|
|
242
|
+
paragraph_shading_top_offset: resolve_attr(
|
|
243
|
+
style_lookup, psr, :paragraph_shading_top_offset
|
|
244
|
+
),
|
|
245
|
+
paragraph_shading_bottom_offset: resolve_attr(
|
|
246
|
+
style_lookup, psr, :paragraph_shading_bottom_offset
|
|
247
|
+
),
|
|
248
|
+
paragraph_border_on: resolve_attr(style_lookup, psr,
|
|
249
|
+
:paragraph_border_on),
|
|
250
|
+
paragraph_border_color: properties_color(psr,
|
|
251
|
+
:paragraph_border_color),
|
|
252
|
+
paragraph_border_tint: resolve_attr(style_lookup, psr,
|
|
253
|
+
:paragraph_border_tint),
|
|
254
|
+
paragraph_border_top_line_weight: resolve_attr(
|
|
255
|
+
style_lookup, psr, :paragraph_border_top_line_weight
|
|
256
|
+
),
|
|
257
|
+
paragraph_border_left_line_weight: resolve_attr(
|
|
258
|
+
style_lookup, psr, :paragraph_border_left_line_weight
|
|
259
|
+
),
|
|
260
|
+
paragraph_border_right_line_weight: resolve_attr(
|
|
261
|
+
style_lookup, psr, :paragraph_border_right_line_weight
|
|
262
|
+
),
|
|
263
|
+
paragraph_border_bottom_line_weight: resolve_attr(
|
|
264
|
+
style_lookup, psr, :paragraph_border_bottom_line_weight
|
|
265
|
+
),
|
|
266
|
+
paragraph_border_top_offset: resolve_attr(
|
|
267
|
+
style_lookup, psr, :paragraph_border_top_offset
|
|
268
|
+
),
|
|
269
|
+
paragraph_border_left_offset: resolve_attr(
|
|
270
|
+
style_lookup, psr, :paragraph_border_left_offset
|
|
271
|
+
),
|
|
272
|
+
paragraph_border_right_offset: resolve_attr(
|
|
273
|
+
style_lookup, psr, :paragraph_border_right_offset
|
|
274
|
+
),
|
|
275
|
+
paragraph_border_bottom_offset: resolve_attr(
|
|
276
|
+
style_lookup, psr, :paragraph_border_bottom_offset
|
|
277
|
+
),
|
|
199
278
|
)
|
|
200
279
|
prepend_list_marker(paragraph)
|
|
201
280
|
paragraph
|
|
202
281
|
end
|
|
203
282
|
end
|
|
204
283
|
|
|
284
|
+
# Reads a color value element from the PSR's Properties (e.g.
|
|
285
|
+
# Properties > ParagraphShadingColor). Returns the color name
|
|
286
|
+
# string, or nil when the element is absent.
|
|
287
|
+
def self.properties_color(psr, element_attr)
|
|
288
|
+
psr.properties.first&.public_send(element_attr)&.value
|
|
289
|
+
end
|
|
290
|
+
private_class_method :properties_color
|
|
291
|
+
|
|
205
292
|
def self.csr_runs(psr, condition_filter: nil, style_lookup: nil,
|
|
206
293
|
footnote_counter: nil, footnote_option: nil)
|
|
207
294
|
counter = footnote_counter || Footnote.counter_for(footnote_option)
|
|
@@ -266,6 +353,9 @@ module Idml
|
|
|
266
353
|
:horizontal_scale),
|
|
267
354
|
vertical_scale: resolve_csr(style_lookup, csr, :vertical_scale),
|
|
268
355
|
baseline_shift: resolve_csr(style_lookup, csr, :baseline_shift),
|
|
356
|
+
ruby_string: resolve_csr(style_lookup, csr, :ruby_string),
|
|
357
|
+
ruby_font_size: resolve_csr(style_lookup, csr, :ruby_font_size),
|
|
358
|
+
ruby_position: resolve_csr(style_lookup, csr, :ruby_position),
|
|
269
359
|
)
|
|
270
360
|
end
|
|
271
361
|
private_class_method :text_run
|
|
@@ -4,19 +4,22 @@ module Idml
|
|
|
4
4
|
module TextEngine
|
|
5
5
|
# Distributes slack space within a line per the paragraph's
|
|
6
6
|
# alignment. Adjusts x_offset and optionally scales inter-word
|
|
7
|
-
# spaces for justified text.
|
|
7
|
+
# spaces for justified text. `last_line:` (the paragraph's final
|
|
8
|
+
# line) keeps ragged right under :justified, as InDesign does.
|
|
8
9
|
class Justifier
|
|
9
|
-
def self.justify(line:, frame_width:, alignment: :left
|
|
10
|
-
|
|
10
|
+
def self.justify(line:, frame_width:, alignment: :left,
|
|
11
|
+
last_line: false)
|
|
12
|
+
new(frame_width, alignment, last_line).justify(line)
|
|
11
13
|
end
|
|
12
14
|
|
|
13
|
-
def initialize(frame_width, alignment)
|
|
15
|
+
def initialize(frame_width, alignment, last_line)
|
|
14
16
|
@frame_width = frame_width
|
|
15
17
|
@alignment = alignment
|
|
18
|
+
@last_line = last_line
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
def justify(line)
|
|
19
|
-
case
|
|
22
|
+
case effective_alignment
|
|
20
23
|
when :left, :start then line.x_offset = 0
|
|
21
24
|
when :center, :middle
|
|
22
25
|
line.x_offset = (@frame_width - line.width) / 2
|
|
@@ -29,6 +32,14 @@ module Idml
|
|
|
29
32
|
|
|
30
33
|
private
|
|
31
34
|
|
|
35
|
+
# The paragraph's last line stays ragged under full
|
|
36
|
+
# justification.
|
|
37
|
+
def effective_alignment
|
|
38
|
+
return :left if @alignment == :justified && @last_line
|
|
39
|
+
|
|
40
|
+
@alignment
|
|
41
|
+
end
|
|
42
|
+
|
|
32
43
|
def justify_line(line)
|
|
33
44
|
spaces = line.glyphs.count(&:is_space)
|
|
34
45
|
slack = @frame_width - line.width
|
|
@@ -7,10 +7,20 @@ module Idml
|
|
|
7
7
|
|
|
8
8
|
# Greedy word-wrap line breaker. Accumulates glyphs until
|
|
9
9
|
# exceeding the frame width, then breaks at the last space.
|
|
10
|
+
# CJK runs get a kinsoku shori post-pass (no line starts or
|
|
11
|
+
# ends with a forbidden character).
|
|
10
12
|
class LineBreaker
|
|
11
13
|
def self.break(glyphs:, frame_width:)
|
|
12
|
-
new(frame_width).break(glyphs)
|
|
14
|
+
lines = new(frame_width).break(glyphs)
|
|
15
|
+
return lines unless cjk_run?(glyphs)
|
|
16
|
+
|
|
17
|
+
CjkLayout.apply_kinsoku(lines)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.cjk_run?(glyphs)
|
|
21
|
+
glyphs.any? { |glyph| CjkLayout.cjk?(glyph.codepoint) }
|
|
13
22
|
end
|
|
23
|
+
private_class_method :cjk_run?
|
|
14
24
|
|
|
15
25
|
def initialize(frame_width)
|
|
16
26
|
@frame_width = frame_width
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Idml
|
|
4
|
+
module TextEngine
|
|
5
|
+
# Height measurement for paragraphs — shape + wrap each run and
|
|
6
|
+
# sum the line count × leading. Used where a paragraph's full
|
|
7
|
+
# extent must be known before its lines are laid out (paragraph
|
|
8
|
+
# shading/border rects).
|
|
9
|
+
module Measurement
|
|
10
|
+
DEFAULT_POINT_SIZE = 12.0
|
|
11
|
+
|
|
12
|
+
# Returns the paragraph's rendered height at `wrap_width`:
|
|
13
|
+
# every run's wrapped line count × leading, plus paragraph
|
|
14
|
+
# SpaceBefore / SpaceAfter.
|
|
15
|
+
def self.paragraph_height(paragraph, font, wrap_width)
|
|
16
|
+
lines_height = paragraph.runs.sum do |run|
|
|
17
|
+
size = run.point_size || DEFAULT_POINT_SIZE
|
|
18
|
+
lines = wrapped_lines(run, font, wrap_width, size)
|
|
19
|
+
lines.length *
|
|
20
|
+
TextEngine::VerticalLayout.leading_for(paragraph.auto_leading,
|
|
21
|
+
size)
|
|
22
|
+
end
|
|
23
|
+
lines_height + space_before_after(paragraph)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.wrapped_lines(run, font, wrap_width, size)
|
|
27
|
+
glyphs = TextEngine::Shaper.shape(text: run.text, font: font,
|
|
28
|
+
size: size)
|
|
29
|
+
TextEngine::LineBreaker.break(glyphs: glyphs,
|
|
30
|
+
frame_width: wrap_width)
|
|
31
|
+
end
|
|
32
|
+
private_class_method :wrapped_lines
|
|
33
|
+
|
|
34
|
+
def self.space_before_after(paragraph)
|
|
35
|
+
(paragraph.space_before || 0) + (paragraph.space_after || 0)
|
|
36
|
+
end
|
|
37
|
+
private_class_method :space_before_after
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/idml/text_engine.rb
CHANGED
|
@@ -6,7 +6,8 @@ module Idml
|
|
|
6
6
|
# .ttf/.otf files. IDML-agnostic — works on any styled text.
|
|
7
7
|
module TextEngine
|
|
8
8
|
autoload :PdfrbFontMetrics, "#{__dir__}/text_engine/pdfrb_font_metrics"
|
|
9
|
-
autoload :Shaper,
|
|
9
|
+
autoload :Shaper, "#{__dir__}/text_engine/shaper"
|
|
10
|
+
autoload :Measurement, "#{__dir__}/text_engine/measurement"
|
|
10
11
|
autoload :ShapedGlyph, "#{__dir__}/text_engine/shaper"
|
|
11
12
|
autoload :LineBreaker, "#{__dir__}/text_engine/line_breaker"
|
|
12
13
|
autoload :Line, "#{__dir__}/text_engine/line_breaker"
|
data/lib/idml/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: idml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bigdecimal
|
|
@@ -173,6 +173,8 @@ files:
|
|
|
173
173
|
- TODO.pdf/114-text-wrap-around-shapes.md
|
|
174
174
|
- TODO.pdf/115-bullet-numbered-lists.md
|
|
175
175
|
- TODO.pdf/116-applied-style-resolution.md
|
|
176
|
+
- TODO.pdf/116-justification-last-line.md
|
|
177
|
+
- TODO.pdf/117-endnote-support.md
|
|
176
178
|
- TODO.pdf/12-idml-to-pdf-pipeline.md
|
|
177
179
|
- TODO.pdf/13-cjk-text-layout.md
|
|
178
180
|
- TODO.pdf/14-page-item-element-models.md
|
|
@@ -291,6 +293,8 @@ files:
|
|
|
291
293
|
- lib/idml/elements/document_object.rb
|
|
292
294
|
- lib/idml/elements/dotted_stroke_style.rb
|
|
293
295
|
- lib/idml/elements/drop_shadow_setting.rb
|
|
296
|
+
- lib/idml/elements/endnote.rb
|
|
297
|
+
- lib/idml/elements/endnote_range.rb
|
|
294
298
|
- lib/idml/elements/feather_setting.rb
|
|
295
299
|
- lib/idml/elements/fill_transparency_setting.rb
|
|
296
300
|
- lib/idml/elements/flex_layout_attribute_option.rb
|
|
@@ -407,6 +411,7 @@ files:
|
|
|
407
411
|
- lib/idml/elements/transform_attribute_option.rb
|
|
408
412
|
- lib/idml/elements/transparency_setting.rb
|
|
409
413
|
- lib/idml/elements/trap_preset.rb
|
|
414
|
+
- lib/idml/elements/typed_value.rb
|
|
410
415
|
- lib/idml/elements/xml_element.rb
|
|
411
416
|
- lib/idml/elements/xml_export_map.rb
|
|
412
417
|
- lib/idml/elements/xml_import_map.rb
|
|
@@ -484,6 +489,7 @@ files:
|
|
|
484
489
|
- lib/idml/text_engine/cjk_layout.rb
|
|
485
490
|
- lib/idml/text_engine/justifier.rb
|
|
486
491
|
- lib/idml/text_engine/line_breaker.rb
|
|
492
|
+
- lib/idml/text_engine/measurement.rb
|
|
487
493
|
- lib/idml/text_engine/pdfrb_font_metrics.rb
|
|
488
494
|
- lib/idml/text_engine/shaper.rb
|
|
489
495
|
- lib/idml/text_engine/vertical_layout.rb
|