idml 0.10.7 → 0.10.8
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/TODO.pdf/142-vertical-latin-run-groups.md +27 -0
- data/TODO.pdf/143-jump-object-below.md +31 -0
- data/TODO.pdf/144-mojikumi-table-model.md +36 -0
- data/TODO.pdf/61-known-limitations.md +14 -9
- data/lib/idml/elements/mojikumi_table.rb +85 -0
- data/lib/idml/elements.rb +4 -0
- data/lib/idml/parts/designmap.rb +3 -0
- data/lib/idml/render/renderers/text_frame_renderer.rb +90 -40
- data/lib/idml/render/text_wrap_resolver.rb +27 -2
- data/lib/idml/version.rb +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e05a75aaa533dcd8f478d6b4ab635d15cf6c64ac85bebbd42fe3b136ed0c6783
|
|
4
|
+
data.tar.gz: 3a478cdb4b23ac26ceb9914d5e3740286ef6fda60c80605a01dc6f2a38086b7a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: effb89c7a3869aa7c268027e4653ecc2072fc41402d53ff2ad8ca0405862b83076bd82637cc9f64a369cd3ceb2ca23b2e6e9ca7ba300de2fd9f073801c65c199
|
|
7
|
+
data.tar.gz: 371206ab57061b8fa94733db6e489565eeb0cd588ffb6b0908a9a0c6e84fcec2d64745501dd8ec7b8147cb3044119c47d08f479e50cec26e939a35f5127ecf9e
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# TODO PDF 142: Run-grouped Latin rotation in vertical writing
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE — implemented 2026-08-27
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
In vertical (StoryOrientation="Vertical") text, every non-CJK glyph
|
|
8
|
+
rotated 90° individually — each Latin letter got its own rotated
|
|
9
|
+
graphics state and stacked one cell apart. InDesign rotates a whole
|
|
10
|
+
Latin run as one block (the word reads sideways as a unit).
|
|
11
|
+
|
|
12
|
+
## Solution
|
|
13
|
+
|
|
14
|
+
`TextFrameRenderer#emit_vertical_stack` partitions a vertical run's
|
|
15
|
+
glyphs: CJK singletons stay upright; consecutive non-CJK glyphs
|
|
16
|
+
(groups never span columns, x-reset on column change) render as ONE
|
|
17
|
+
text op inside a single rotated graphics state. The font's natural
|
|
18
|
+
advances land each glyph exactly where per-glyph rotation placed
|
|
19
|
+
it, so the change is lossless for position — but a whole word now
|
|
20
|
+
rotates as a unit and emits one op instead of N. The per-glyph
|
|
21
|
+
`emit_vertical_glyph` path is gone (dead after grouping covered it).
|
|
22
|
+
|
|
23
|
+
## Files
|
|
24
|
+
|
|
25
|
+
- `lib/idml/render/renderers/text_frame_renderer.rb`
|
|
26
|
+
- `spec/idml/render/vertical_render_spec.rb`
|
|
27
|
+
- `spec/idml/render/vertical_multirun_spec.rb`
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# TODO PDF 143: JumpObject wrap — text below the object
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE — implemented 2026-08-27
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
JumpObjectTextWrap approximated as the bounding box: text narrowed
|
|
8
|
+
beside the object. InDesign's JumpObject means text NEVER flows
|
|
9
|
+
beside the object — it continues below it.
|
|
10
|
+
|
|
11
|
+
## Solution
|
|
12
|
+
|
|
13
|
+
- `TextWrapResolver::Contour` gains a `jump` flag, set from
|
|
14
|
+
`TextWrapMode="JumpObjectTextWrap"`.
|
|
15
|
+
- A jump contour blocks the FULL frame width in
|
|
16
|
+
`wrap_adjustment`.
|
|
17
|
+
- `TextFrameRenderer#skip_below_jump_object`: when the run's band
|
|
18
|
+
is fully blocked (wrap width below the point size), the cursor
|
|
19
|
+
skips to `jump_contour_bottom` — the blocking object's bottom
|
|
20
|
+
edge — and the wrap re-measures there once. Text resumes below
|
|
21
|
+
the object, never beside it.
|
|
22
|
+
|
|
23
|
+
NextColumn still approximates as the box (it needs column-jump
|
|
24
|
+
chaining, documented in TODO.pdf/61).
|
|
25
|
+
|
|
26
|
+
## Files
|
|
27
|
+
|
|
28
|
+
- `lib/idml/render/text_wrap_resolver.rb`
|
|
29
|
+
- `lib/idml/render/renderers/text_frame_renderer.rb`
|
|
30
|
+
- `spec/idml/render/text_wrap_resolver_spec.rb`
|
|
31
|
+
- `spec/idml/render/jump_object_render_spec.rb` (end-to-end)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# TODO PDF 144: Named mojikumi sets modeled
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE (modeling) — implemented 2026-08-27
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
Designmap-level `<MojikumiTable>` elements (named mojikumi sets:
|
|
8
|
+
詳細 etc. with per-class-pair aki overrides) were ignored by the
|
|
9
|
+
model — a document declaring custom spacing lost that information
|
|
10
|
+
on round-trip.
|
|
11
|
+
|
|
12
|
+
## Solution
|
|
13
|
+
|
|
14
|
+
New elements per `designmap.rnc` / `datatype.rnc`:
|
|
15
|
+
|
|
16
|
+
- `Elements::MojikumiTable` — Self / Name / BasedOnMojikumiSet +
|
|
17
|
+
`#aki_overrides` (flattened document-order overrides).
|
|
18
|
+
- `Elements::MojikumiTableProperties` — OverrideMojikumiAkiList.
|
|
19
|
+
- `Elements::OverrideMojikumiAkiList` — the entries.
|
|
20
|
+
- `Elements::OverrideMojikumiAki` — all 8 typedef attributes
|
|
21
|
+
(Target/SideMojikumiClass, SideIsAfterTarget, Minimum/Desired/
|
|
22
|
+
Maximum, CompressionPriority, AkiDoesNotFloat).
|
|
23
|
+
|
|
24
|
+
`Parts::Designmap` maps the children; round-trip verified.
|
|
25
|
+
|
|
26
|
+
**Not yet applied**: rendering still uses the built-in class-based
|
|
27
|
+
aki rules (TODOs 125/132/13); consuming BasedOnMojikumiSet and the
|
|
28
|
+
override entries in `CjkLayout` is the follow-up (TODO.pdf/61).
|
|
29
|
+
|
|
30
|
+
## Files
|
|
31
|
+
|
|
32
|
+
- `lib/idml/elements/mojikumi_table.rb`
|
|
33
|
+
- `lib/idml/parts/designmap.rb`
|
|
34
|
+
- `lib/idml/elements.rb`
|
|
35
|
+
- `spec/idml/elements/mojikumi_table_spec.rb`
|
|
36
|
+
- `spec/idml/parts/designmap_spec.rb`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TODO PDF 61: Known limitations and future work
|
|
2
2
|
|
|
3
|
-
## Status: DOCUMENTED — refreshed 2026-08-27
|
|
3
|
+
## Status: DOCUMENTED — refreshed 2026-08-27 (second refresh)
|
|
4
4
|
|
|
5
5
|
Previously listed limitations (font embedding, tagged PDF, per-run
|
|
6
6
|
styling, dead code) were resolved by TODOs 52/53/55/67/76 long ago;
|
|
@@ -27,15 +27,19 @@ this list reflects the current state.
|
|
|
27
27
|
- **Text wrap**: BoundingBoxTextWrap, Contour, and Inverse modes
|
|
28
28
|
render (TODOs 130-131) with TextWrapSide side-awareness (TODO 138:
|
|
29
29
|
LeftSide / RightSide / LargestArea pick the flow side; spine
|
|
30
|
-
variants approximate as BothSides); JumpObject
|
|
31
|
-
|
|
30
|
+
variants approximate as BothSides); JumpObject moves text below
|
|
31
|
+
the object (TODO 143); NextColumn approximates as the box;
|
|
32
|
+
Contour-mode shapes are not side-aware.
|
|
32
33
|
|
|
33
34
|
### CJK
|
|
34
35
|
- **Mojikumi**: script spacing (TODO 125), line-end compression
|
|
35
|
-
(TODO 132), and class-based pair compression
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
(TODO 132), and class-based pair compression are applied; named
|
|
37
|
+
mojikumi sets are modeled (TODO 144: MojikumiTable +
|
|
38
|
+
OverrideMojikumiAki parse and round-trip) but not yet APPLIED to
|
|
39
|
+
layout — the built-in class-based aki rules stand in.
|
|
40
|
+
- **Vertical mode**: Latin runs rotate as one group per segment
|
|
41
|
+
(TODO 142); ruby placement is beside-column; keep options do
|
|
42
|
+
not apply.
|
|
39
43
|
|
|
40
44
|
### Structural features
|
|
41
45
|
- **Endnotes** (TODO 117): reference markers render and endnote TEXT
|
|
@@ -56,6 +60,7 @@ this list reflects the current state.
|
|
|
56
60
|
|
|
57
61
|
- Hyphenation dictionary integration
|
|
58
62
|
- Shape-mode text wrap contours (side-aware)
|
|
59
|
-
-
|
|
63
|
+
- Apply named mojikumi sets to CJK layout (consume TODO 144's model)
|
|
64
|
+
- NextColumn wrap via column-jump chaining
|
|
60
65
|
- ToBinding / RTL binding alignment
|
|
61
|
-
- Vertical-mode keep options
|
|
66
|
+
- Vertical-mode keep options
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Idml
|
|
4
|
+
module Elements
|
|
5
|
+
# One aki override (per `OverrideMojikumiAkiType_TypeDef`):
|
|
6
|
+
# spacing between the Target and Side character classes, as
|
|
7
|
+
# minimum / desired / maximum.
|
|
8
|
+
class OverrideMojikumiAki < Lutaml::Model::Serializable
|
|
9
|
+
attribute :target_mojikumi_class, :integer
|
|
10
|
+
attribute :side_mojikumi_class, :integer
|
|
11
|
+
attribute :side_is_after_target, :boolean
|
|
12
|
+
attribute :minimum, :float
|
|
13
|
+
attribute :desired, :float
|
|
14
|
+
attribute :maximum, :float
|
|
15
|
+
attribute :compression_priority, :integer
|
|
16
|
+
attribute :aki_does_not_float, :boolean
|
|
17
|
+
|
|
18
|
+
xml do
|
|
19
|
+
root "OverrideMojikumiAkiType"
|
|
20
|
+
map_attribute "TargetMojikumiClass", to: :target_mojikumi_class
|
|
21
|
+
map_attribute "SideMojikumiClass", to: :side_mojikumi_class
|
|
22
|
+
map_attribute "SideIsAfterTarget", to: :side_is_after_target
|
|
23
|
+
map_attribute "Minimum", to: :minimum
|
|
24
|
+
map_attribute "Desired", to: :desired
|
|
25
|
+
map_attribute "Maximum", to: :maximum
|
|
26
|
+
map_attribute "CompressionPriority", to: :compression_priority
|
|
27
|
+
map_attribute "AkiDoesNotFloat", to: :aki_does_not_float
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# `<OverrideMojikumiAkiList>` — the per-class-pair override
|
|
32
|
+
# entries of a named mojikumi set.
|
|
33
|
+
class OverrideMojikumiAkiList < Lutaml::Model::Serializable
|
|
34
|
+
attribute :override_mojikumi_aki, OverrideMojikumiAki,
|
|
35
|
+
collection: true
|
|
36
|
+
|
|
37
|
+
xml do
|
|
38
|
+
root "OverrideMojikumiAkiList"
|
|
39
|
+
map_element "OverrideMojikumiAkiType",
|
|
40
|
+
to: :override_mojikumi_aki
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# `<Properties>` inside a MojikumiTable — carries the aki
|
|
45
|
+
# override list.
|
|
46
|
+
class MojikumiTableProperties < Lutaml::Model::Serializable
|
|
47
|
+
attribute :override_mojikumi_aki_list, OverrideMojikumiAkiList,
|
|
48
|
+
collection: true
|
|
49
|
+
|
|
50
|
+
xml do
|
|
51
|
+
root "Properties"
|
|
52
|
+
map_element "OverrideMojikumiAkiList",
|
|
53
|
+
to: :override_mojikumi_aki_list
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Typed model of `<MojikumiTable>` (TODO 144) — a named
|
|
58
|
+
# mojikumi set declared at the designmap level. Named sets are
|
|
59
|
+
# modeled (parsed and round-tripped) but not yet APPLIED: the
|
|
60
|
+
# renderer keeps using the built-in class-based aki rules (see
|
|
61
|
+
# TODO.pdf/61).
|
|
62
|
+
class MojikumiTable < Lutaml::Model::Serializable
|
|
63
|
+
attribute :self_attr, :string
|
|
64
|
+
attribute :name, :string
|
|
65
|
+
attribute :based_on_mojikumi_set, :string
|
|
66
|
+
attribute :properties, MojikumiTableProperties, collection: true
|
|
67
|
+
|
|
68
|
+
xml do
|
|
69
|
+
root "MojikumiTable"
|
|
70
|
+
map_attribute "Self", to: :self_attr
|
|
71
|
+
map_attribute "Name", to: :name
|
|
72
|
+
map_attribute "BasedOnMojikumiSet", to: :based_on_mojikumi_set
|
|
73
|
+
map_element "Properties", to: :properties
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Aki overrides in document order, flattened across the
|
|
77
|
+
# Properties > OverrideMojikumiAkiList chain.
|
|
78
|
+
def aki_overrides
|
|
79
|
+
properties.flat_map do |prop|
|
|
80
|
+
prop.override_mojikumi_aki_list.flat_map(&:override_mojikumi_aki)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
data/lib/idml/elements.rb
CHANGED
|
@@ -90,6 +90,10 @@ module Idml
|
|
|
90
90
|
autoload :MasterSpreadObject, "idml/elements/master_spread_object"
|
|
91
91
|
autoload :MixedInk, "idml/elements/mixed_ink"
|
|
92
92
|
autoload :MixedInkGroup, "idml/elements/mixed_ink_group"
|
|
93
|
+
autoload :MojikumiTable, "idml/elements/mojikumi_table"
|
|
94
|
+
autoload :MojikumiTableProperties, "idml/elements/mojikumi_table"
|
|
95
|
+
autoload :OverrideMojikumiAki, "idml/elements/mojikumi_table"
|
|
96
|
+
autoload :OverrideMojikumiAkiList, "idml/elements/mojikumi_table"
|
|
93
97
|
autoload :MojikumiUiPreference, "idml/elements/pref_mojikumi_ui_preference"
|
|
94
98
|
autoload :ObjectExportOption, "idml/elements/object_export_option"
|
|
95
99
|
autoload :ObjectStyle, "idml/elements/object_style"
|
data/lib/idml/parts/designmap.rb
CHANGED
|
@@ -54,6 +54,8 @@ module Idml
|
|
|
54
54
|
Idml::Elements::HyperlinkPageDestination, collection: true
|
|
55
55
|
attribute :hyperlink_url_destination,
|
|
56
56
|
Idml::Elements::HyperlinkURLDestination, collection: true
|
|
57
|
+
attribute :mojikumi_table, Idml::Elements::MojikumiTable,
|
|
58
|
+
collection: true
|
|
57
59
|
|
|
58
60
|
xml do
|
|
59
61
|
root "Document"
|
|
@@ -99,6 +101,7 @@ module Idml
|
|
|
99
101
|
to: :hyperlink_page_destination
|
|
100
102
|
map_element "HyperlinkURLDestination",
|
|
101
103
|
to: :hyperlink_url_destination
|
|
104
|
+
map_element "MojikumiTable", to: :mojikumi_table
|
|
102
105
|
end
|
|
103
106
|
end
|
|
104
107
|
end
|
|
@@ -332,13 +332,60 @@ module Idml
|
|
|
332
332
|
emit_upright_glyph(canvas, glyph, run, context, size)
|
|
333
333
|
end
|
|
334
334
|
else
|
|
335
|
-
positioned
|
|
336
|
-
emit_vertical_glyph(canvas, glyph, run, context, size)
|
|
337
|
-
end
|
|
335
|
+
emit_vertical_stack(canvas, positioned, run, context, size)
|
|
338
336
|
end
|
|
339
337
|
end
|
|
340
338
|
private_class_method :emit_vertical_run
|
|
341
339
|
|
|
340
|
+
# Vertical-stack emission: CJK glyphs stand upright per
|
|
341
|
+
# glyph; consecutive non-CJK glyphs rotate as ONE group
|
|
342
|
+
# (the whole Latin word sideways, as InDesign does). The
|
|
343
|
+
# group's string renders as a single text op inside one
|
|
344
|
+
# rotated graphics state — the font's advances land each
|
|
345
|
+
# glyph exactly where per-glyph rotation placed it. A group
|
|
346
|
+
# never spans columns (x reset) and shares the run's
|
|
347
|
+
# styling, so one text op is lossless.
|
|
348
|
+
def self.emit_vertical_stack(canvas, positioned, run, context,
|
|
349
|
+
size)
|
|
350
|
+
segment = []
|
|
351
|
+
positioned.each do |glyph|
|
|
352
|
+
if TextEngine::CjkLayout.cjk?(glyph.codepoint)
|
|
353
|
+
flush_vertical_segment(canvas, segment, run, context, size)
|
|
354
|
+
emit_upright_glyph(canvas, glyph, run, context, size)
|
|
355
|
+
else
|
|
356
|
+
if !segment.empty? && segment.last.x != glyph.x
|
|
357
|
+
flush_vertical_segment(canvas, segment, run, context, size)
|
|
358
|
+
end
|
|
359
|
+
segment << glyph
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
flush_vertical_segment(canvas, segment, run, context, size)
|
|
363
|
+
end
|
|
364
|
+
private_class_method :emit_vertical_stack
|
|
365
|
+
|
|
366
|
+
def self.flush_vertical_segment(canvas, segment, run, context,
|
|
367
|
+
size)
|
|
368
|
+
return if segment.empty?
|
|
369
|
+
|
|
370
|
+
text = segment.map { |g| [g.codepoint].pack("U") }.join
|
|
371
|
+
first = segment.first
|
|
372
|
+
canvas.save_graphics_state do
|
|
373
|
+
# 90° clockwise: exact matrix (rotate would emit
|
|
374
|
+
# cos/sin float noise like 6.1e-17).
|
|
375
|
+
canvas.concat(0, -1, 1, 0, first.x, first.y)
|
|
376
|
+
text_kwargs = CharacterStyle.text_kwargs(
|
|
377
|
+
run,
|
|
378
|
+
{
|
|
379
|
+
at: [0, 0],
|
|
380
|
+
font: font_for_run(run, context),
|
|
381
|
+
size: size,
|
|
382
|
+
},
|
|
383
|
+
)
|
|
384
|
+
canvas.text(text, **text_kwargs)
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
private_class_method :flush_vertical_segment
|
|
388
|
+
|
|
342
389
|
def self.emit_upright_glyph(canvas, positioned, run, context,
|
|
343
390
|
size)
|
|
344
391
|
text = [positioned.codepoint].pack("U")
|
|
@@ -361,43 +408,6 @@ module Idml
|
|
|
361
408
|
end
|
|
362
409
|
private_class_method :vertical_fits?
|
|
363
410
|
|
|
364
|
-
# Emits one upright glyph at its vertical position with the
|
|
365
|
-
# run's character styling.
|
|
366
|
-
# Emits one glyph in vertical mode: CJK upright; Latin
|
|
367
|
-
# (non-CJK) rotated 90° clockwise per the vertical-writing
|
|
368
|
-
# convention, via a graphics-state transform.
|
|
369
|
-
def self.emit_vertical_glyph(canvas, positioned, run, context,
|
|
370
|
-
size)
|
|
371
|
-
text = [positioned.codepoint].pack("U")
|
|
372
|
-
if TextEngine::CjkLayout.cjk?(positioned.codepoint)
|
|
373
|
-
text_kwargs = CharacterStyle.text_kwargs(
|
|
374
|
-
run,
|
|
375
|
-
{
|
|
376
|
-
at: [positioned.x, positioned.y],
|
|
377
|
-
font: font_for_run(run, context),
|
|
378
|
-
size: size,
|
|
379
|
-
},
|
|
380
|
-
)
|
|
381
|
-
canvas.text(text, **text_kwargs)
|
|
382
|
-
else
|
|
383
|
-
canvas.save_graphics_state do
|
|
384
|
-
# 90° clockwise: exact matrix (rotate would emit
|
|
385
|
-
# cos/sin float noise like 6.1e-17).
|
|
386
|
-
canvas.concat(0, -1, 1, 0, positioned.x, positioned.y)
|
|
387
|
-
text_kwargs = CharacterStyle.text_kwargs(
|
|
388
|
-
run,
|
|
389
|
-
{
|
|
390
|
-
at: [0, 0],
|
|
391
|
-
font: font_for_run(run, context),
|
|
392
|
-
size: size,
|
|
393
|
-
},
|
|
394
|
-
)
|
|
395
|
-
canvas.text(text, **text_kwargs)
|
|
396
|
-
end
|
|
397
|
-
end
|
|
398
|
-
end
|
|
399
|
-
private_class_method :emit_vertical_glyph
|
|
400
|
-
|
|
401
411
|
# Emits the run's ruby alongside its vertical glyphs:
|
|
402
412
|
# stacked top-to-bottom beside the column — right side by
|
|
403
413
|
# default, left for Below* RubyPosition values (the
|
|
@@ -881,6 +891,10 @@ module Idml
|
|
|
881
891
|
context, layout_frame, cursor_y, run
|
|
882
892
|
)
|
|
883
893
|
run_wrap -= wrap_reduction
|
|
894
|
+
cursor_y, run_wrap, wrap_shift = skip_below_jump_object(
|
|
895
|
+
context, layout_frame, cursor_y, run, run_wrap, wrap_shift,
|
|
896
|
+
wrap_reduction, bottom_limit
|
|
897
|
+
)
|
|
884
898
|
positioned, next_y = layout_run(
|
|
885
899
|
canvas, run, paragraph, context, layout_frame, font,
|
|
886
900
|
run_wrap, cursor_y, space_before,
|
|
@@ -946,6 +960,42 @@ module Idml
|
|
|
946
960
|
end
|
|
947
961
|
private_class_method :render_footnote_entries
|
|
948
962
|
|
|
963
|
+
# JumpObjectTextWrap blocks the full column width: when the
|
|
964
|
+
# run's band is fully blocked, move the cursor below the
|
|
965
|
+
# object's bottom edge and re-measure once. InDesign's
|
|
966
|
+
# semantics — text flows under the object, never beside it.
|
|
967
|
+
# Returns the (possibly skipped) cursor and re-measured
|
|
968
|
+
# wrap geometry.
|
|
969
|
+
def self.skip_below_jump_object(context, layout_frame, cursor_y,
|
|
970
|
+
run, run_wrap, wrap_shift,
|
|
971
|
+
wrap_reduction, bottom_limit)
|
|
972
|
+
min_width = run.point_size || DEFAULT_SIZE
|
|
973
|
+
return [cursor_y, run_wrap, wrap_shift] if run_wrap >= min_width
|
|
974
|
+
|
|
975
|
+
skip_y = text_wrap_jump_below(context, layout_frame, cursor_y, run)
|
|
976
|
+
return [cursor_y, run_wrap, wrap_shift] unless skip_y
|
|
977
|
+
return [cursor_y, run_wrap, wrap_shift] if skip_y >= cursor_y
|
|
978
|
+
return [cursor_y, run_wrap, wrap_shift] if skip_y <= bottom_limit
|
|
979
|
+
|
|
980
|
+
reduction, shift = text_wrap_adjust(context, layout_frame,
|
|
981
|
+
skip_y, run)
|
|
982
|
+
[skip_y, [run_wrap + wrap_reduction - reduction, 0].max, shift]
|
|
983
|
+
end
|
|
984
|
+
private_class_method :skip_below_jump_object
|
|
985
|
+
|
|
986
|
+
def self.text_wrap_jump_below(context, layout_frame, cursor_y, run)
|
|
987
|
+
resolver = context.text_wrap_resolver
|
|
988
|
+
return nil unless resolver
|
|
989
|
+
|
|
990
|
+
size = run.point_size || DEFAULT_SIZE
|
|
991
|
+
frame_x = layout_frame.x + (layout_frame.inset_left || 0)
|
|
992
|
+
frame_right = layout_frame.x + layout_frame.width -
|
|
993
|
+
(layout_frame.inset_right || 0)
|
|
994
|
+
resolver.jump_contour_bottom(cursor_y, size, frame_x,
|
|
995
|
+
frame_right)
|
|
996
|
+
end
|
|
997
|
+
private_class_method :text_wrap_jump_below
|
|
998
|
+
|
|
949
999
|
# Computes the text-wrap adjustment for a run at its current y
|
|
950
1000
|
# position: [width_reduction, x_shift]. The reduction narrows
|
|
951
1001
|
# the wrap width; the shift moves wrapped lines right past a
|
|
@@ -18,7 +18,7 @@ module Idml
|
|
|
18
18
|
class TextWrapResolver
|
|
19
19
|
# A wrap contour: a PDF-coordinate rectangle the text avoids.
|
|
20
20
|
Contour = Struct.new(
|
|
21
|
-
:x, :y, :width, :height, :inverse, :side,
|
|
21
|
+
:x, :y, :width, :height, :inverse, :side, :jump,
|
|
22
22
|
keyword_init: true
|
|
23
23
|
)
|
|
24
24
|
|
|
@@ -86,10 +86,34 @@ module Idml
|
|
|
86
86
|
|
|
87
87
|
return [0.0, 0.0] if overlap.zero?
|
|
88
88
|
|
|
89
|
+
# JumpObject: text never flows beside the object — the full
|
|
90
|
+
# frame width is blocked and the renderer skips the run
|
|
91
|
+
# below the object's bottom edge.
|
|
92
|
+
return [frame_right - frame_x, 0.0] if contour.jump
|
|
93
|
+
|
|
89
94
|
side_adjustment(contour, overlap, frame_x, frame_right)
|
|
90
95
|
end
|
|
91
96
|
private :box_adjustment
|
|
92
97
|
|
|
98
|
+
# Bottom edge of the lowest jump contour overlapping the given
|
|
99
|
+
# band — where text resumes below a JumpObject. nil when no
|
|
100
|
+
# jump contour blocks the band.
|
|
101
|
+
def jump_contour_bottom(line_y, line_height, frame_x, frame_right)
|
|
102
|
+
bottoms = @contours.filter_map do |contour|
|
|
103
|
+
next unless box_contour?(contour) && contour.jump
|
|
104
|
+
next unless y_overlap?(contour, line_y, line_height) &&
|
|
105
|
+
x_overlap?(contour, frame_x, frame_right)
|
|
106
|
+
|
|
107
|
+
contour.y
|
|
108
|
+
end
|
|
109
|
+
bottoms.min
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def box_contour?(contour)
|
|
113
|
+
!contour.is_a?(WrapContour::Shape)
|
|
114
|
+
end
|
|
115
|
+
private :box_contour?
|
|
116
|
+
|
|
93
117
|
def side_adjustment(contour, overlap, frame_x, frame_right)
|
|
94
118
|
case contour.side
|
|
95
119
|
when "LeftSide"
|
|
@@ -165,7 +189,8 @@ module Idml
|
|
|
165
189
|
Contour.new(
|
|
166
190
|
x: rect[:x], y: rect[:y],
|
|
167
191
|
width: rect[:width], height: rect[:height],
|
|
168
|
-
side: pref&.text_wrap_side
|
|
192
|
+
side: pref&.text_wrap_side,
|
|
193
|
+
jump: pref&.text_wrap_mode == "JumpObjectTextWrap"
|
|
169
194
|
)
|
|
170
195
|
end
|
|
171
196
|
private_class_method :box_contour
|
data/lib/idml/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: idml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.10.
|
|
4
|
+
version: 0.10.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose
|
|
@@ -205,6 +205,9 @@ files:
|
|
|
205
205
|
- TODO.pdf/14-page-item-element-models.md
|
|
206
206
|
- TODO.pdf/140-cap-xheight-first-baselines.md
|
|
207
207
|
- TODO.pdf/141-perf-hundred-pages.md
|
|
208
|
+
- TODO.pdf/142-vertical-latin-run-groups.md
|
|
209
|
+
- TODO.pdf/143-jump-object-below.md
|
|
210
|
+
- TODO.pdf/144-mojikumi-table-model.md
|
|
208
211
|
- TODO.pdf/15-wire-spread-children.md
|
|
209
212
|
- TODO.pdf/16-typed-model-pipeline.md
|
|
210
213
|
- TODO.pdf/17-shape-rendering.md
|
|
@@ -348,6 +351,7 @@ files:
|
|
|
348
351
|
- lib/idml/elements/master_spread_object.rb
|
|
349
352
|
- lib/idml/elements/mixed_ink.rb
|
|
350
353
|
- lib/idml/elements/mixed_ink_group.rb
|
|
354
|
+
- lib/idml/elements/mojikumi_table.rb
|
|
351
355
|
- lib/idml/elements/object_export_option.rb
|
|
352
356
|
- lib/idml/elements/object_style.rb
|
|
353
357
|
- lib/idml/elements/object_style_content_effects_category_settings.rb
|