idml 0.10.7 → 0.10.9
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/145-apply-mojikumi-aki.md +37 -0
- data/TODO.pdf/146-next-column-wrap.md +25 -0
- data/TODO.pdf/147-side-aware-shapes.md +27 -0
- data/TODO.pdf/61-known-limitations.md +18 -13
- 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 +106 -40
- data/lib/idml/render/text_wrap_resolver.rb +72 -26
- data/lib/idml/render/wrap_contour.rb +13 -3
- data/lib/idml/text_engine/cjk_layout.rb +79 -6
- data/lib/idml/version.rb +1 -1
- metadata +9 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c3ebdf83496770ecda7b37cc6f6cdc2388874785ccad23ac57aa396f76aef03b
|
|
4
|
+
data.tar.gz: d7304a209e09d4de3ee6d9f7556d18053f838ee3fef9a4f787f06693aa42ee32
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bd4dc75a85966131506edf7f3e01df91127fbf26525bb02edcf783f0c07580380be0d8bd38562c11a477602731e2efff8de78061580b471f1389f3bb87a07119
|
|
7
|
+
data.tar.gz: 74286965e709455a356789cc6f7e4c93f75d414a1132301ee8e347a85d114065bd1988921c180a522c646824d79f2049bdfacf7db609e9981b270d3a88b71fbb
|
|
@@ -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`
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# TODO PDF 145: Apply mojikumi aki to layout
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE — implemented 2026-08-28
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
Two gaps: (a) `apply_script_spacing` (TODO 125) was implemented
|
|
8
|
+
but never wired into the render pipeline — CJK/Latin auto spacing
|
|
9
|
+
never actually applied; (b) the named mojikumi sets modeled in
|
|
10
|
+
TODO 144 were parsed but unused.
|
|
11
|
+
|
|
12
|
+
## Solution
|
|
13
|
+
|
|
14
|
+
- `CjkLayout.apply_script_spacing` now takes the document's aki
|
|
15
|
+
overrides: when the designmap declares a `MojikumiTable`, each
|
|
16
|
+
adjacent glyph pair whose classes match an
|
|
17
|
+
`OverrideMojikumiAki` entry (Target/Side class ×
|
|
18
|
+
SideIsAfterTarget) gets that entry's Desired spacing (em ×
|
|
19
|
+
point size). Unlisted pairs keep the default eighth-em at
|
|
20
|
+
CJK/Latin script boundaries.
|
|
21
|
+
- New `CjkLayout.mojikumi_class` classifies the seven InDesign
|
|
22
|
+
mojikumi classes (ideograph, opening bracket, closing bracket,
|
|
23
|
+
comma/period, middle dot, digit — incl. fullwidth, Latin —
|
|
24
|
+
incl. fullwidth).
|
|
25
|
+
- `TextFrameRenderer#layout_run` applies the spacing between
|
|
26
|
+
shaping and line breaking, using
|
|
27
|
+
`mojikumi_aki_overrides(context)` (first named set from the
|
|
28
|
+
designmap; empty when absent).
|
|
29
|
+
|
|
30
|
+
The result: documents declaring custom mojikumi now render their
|
|
31
|
+
declared inter-class spacing.
|
|
32
|
+
|
|
33
|
+
## Files
|
|
34
|
+
|
|
35
|
+
- `lib/idml/text_engine/cjk_layout.rb`
|
|
36
|
+
- `lib/idml/render/renderers/text_frame_renderer.rb`
|
|
37
|
+
- `spec/idml/text_engine/cjk_layout_spec.rb`
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# TODO PDF 146: NextColumn wrap behavior
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE (approximation) — implemented 2026-08-28
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
NextColumnTextWrap approximated as the bounding box (text narrowed
|
|
8
|
+
beside the object). InDesign moves the text to the next COLUMN.
|
|
9
|
+
|
|
10
|
+
## Solution
|
|
11
|
+
|
|
12
|
+
NextColumnTextWrap now joins JumpObjectTextWrap in the jump path:
|
|
13
|
+
the object blocks the full frame width and the run skips below
|
|
14
|
+
the object's bottom edge. In a single-column frame this matches
|
|
15
|
+
InDesign's visible outcome (the "next column" is below).
|
|
16
|
+
|
|
17
|
+
Remaining approximation (documented in TODO.pdf/61): in a
|
|
18
|
+
MULTI-column frame, true NextColumn jumps to the next column in
|
|
19
|
+
the same frame — that needs column-jump chain integration, which
|
|
20
|
+
stays a future enhancement.
|
|
21
|
+
|
|
22
|
+
## Files
|
|
23
|
+
|
|
24
|
+
- `lib/idml/render/text_wrap_resolver.rb`
|
|
25
|
+
- `spec/idml/render/text_wrap_resolver_spec.rb`
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# TODO PDF 147: Side-aware Contour shapes
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE — implemented 2026-08-28
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
TextWrapSide applied only to BoundingBoxTextWrap contours;
|
|
8
|
+
Contour-mode shapes narrowed by polygon overlap regardless of
|
|
9
|
+
side — text smeared over shapes at frame edges instead of
|
|
10
|
+
flowing around the chosen side.
|
|
11
|
+
|
|
12
|
+
## Solution
|
|
13
|
+
|
|
14
|
+
`WrapContour::Shape` carries `side`; `wrap_adjustment`'s shape
|
|
15
|
+
branch dispatches through the shared interval-based side logic
|
|
16
|
+
using the polygon's bounding box: LeftSide keeps text left of the
|
|
17
|
+
shape, RightSide shifts lines past its right edge, LargestArea
|
|
18
|
+
picks the roomier side; BothSides (and spine variants) keep the
|
|
19
|
+
exact polygon-overlap narrowing. Inverse shapes keep their
|
|
20
|
+
flip-to-inside semantics. The side dispatch is now one shared
|
|
21
|
+
`interval_side_adjustment` for boxes and shapes alike.
|
|
22
|
+
|
|
23
|
+
## Files
|
|
24
|
+
|
|
25
|
+
- `lib/idml/render/wrap_contour.rb`
|
|
26
|
+
- `lib/idml/render/text_wrap_resolver.rb`
|
|
27
|
+
- `spec/idml/render/text_wrap_resolver_spec.rb`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TODO PDF 61: Known limitations and future work
|
|
2
2
|
|
|
3
|
-
## Status: DOCUMENTED — refreshed 2026-08-
|
|
3
|
+
## Status: DOCUMENTED — refreshed 2026-08-28
|
|
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;
|
|
@@ -25,17 +25,22 @@ this list reflects the current state.
|
|
|
25
25
|
- **StartParagraph** breaks act at frame/column granularity — no
|
|
26
26
|
odd/even page parity.
|
|
27
27
|
- **Text wrap**: BoundingBoxTextWrap, Contour, and Inverse modes
|
|
28
|
-
render (TODOs 130-131) with TextWrapSide side-awareness
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
render (TODOs 130-131) with TextWrapSide side-awareness for both
|
|
29
|
+
box contours and Contour shapes (TODOs 138/147); JumpObject and
|
|
30
|
+
NextColumn move text below the object (TODOs 143/146) — NextColumn
|
|
31
|
+
in a MULTI-column frame should jump to the next column instead
|
|
32
|
+
(chain integration, future); spine variants approximate as
|
|
33
|
+
BothSides.
|
|
32
34
|
|
|
33
35
|
### CJK
|
|
34
|
-
- **Mojikumi**: script spacing (TODO 125
|
|
35
|
-
(TODO 132),
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
- **Mojikumi**: script spacing (TODO 125, now actually wired),
|
|
37
|
+
line-end compression (TODO 132), class-based pair compression,
|
|
38
|
+
and named-set aki overrides (TODO 145: OverrideMojikumiAki
|
|
39
|
+
Desired applies per class pair) are applied; Minimum/Maximum
|
|
40
|
+
aki bounds and CompressionPriority are parsed but unused.
|
|
41
|
+
- **Vertical mode**: Latin runs rotate as one group per segment
|
|
42
|
+
(TODO 142); ruby placement is beside-column; keep options do
|
|
43
|
+
not apply.
|
|
39
44
|
|
|
40
45
|
### Structural features
|
|
41
46
|
- **Endnotes** (TODO 117): reference markers render and endnote TEXT
|
|
@@ -55,7 +60,7 @@ this list reflects the current state.
|
|
|
55
60
|
## Future enhancements
|
|
56
61
|
|
|
57
62
|
- Hyphenation dictionary integration
|
|
58
|
-
-
|
|
59
|
-
-
|
|
63
|
+
- NextColumn wrap via column-jump chaining (multi-column frames)
|
|
64
|
+
- Minimum/Maximum mojikumi aki bounds + CompressionPriority
|
|
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
|
|
@@ -1095,6 +1145,9 @@ module Idml
|
|
|
1095
1145
|
glyphs = TextEngine::Shaper.shape(
|
|
1096
1146
|
text: run.text, font: font, size: size,
|
|
1097
1147
|
)
|
|
1148
|
+
glyphs = TextEngine::CjkLayout.apply_script_spacing(
|
|
1149
|
+
glyphs, size, mojikumi_aki_overrides(context)
|
|
1150
|
+
)
|
|
1098
1151
|
lines = TextEngine::LineBreaker.break(
|
|
1099
1152
|
glyphs: glyphs, frame_width: wrap_width,
|
|
1100
1153
|
)
|
|
@@ -1119,6 +1172,19 @@ module Idml
|
|
|
1119
1172
|
end
|
|
1120
1173
|
private_class_method :layout_run
|
|
1121
1174
|
|
|
1175
|
+
# Aki overrides from the document's first named mojikumi
|
|
1176
|
+
# set, when the designmap declares one (TODO 145). Empty
|
|
1177
|
+
# otherwise — apply_script_spacing then uses the default
|
|
1178
|
+
# CJK/Latin script spacing.
|
|
1179
|
+
def self.mojikumi_aki_overrides(context)
|
|
1180
|
+
package = context.package
|
|
1181
|
+
return [] unless package&.has_part?("designmap.xml")
|
|
1182
|
+
|
|
1183
|
+
table = package.designmap.mojikumi_table.first
|
|
1184
|
+
table&.aki_overrides || []
|
|
1185
|
+
end
|
|
1186
|
+
private_class_method :mojikumi_aki_overrides
|
|
1187
|
+
|
|
1122
1188
|
# True when the paragraph should move wholly to the next
|
|
1123
1189
|
# frame: a StartParagraph forced break, or KeepAllLines-
|
|
1124
1190
|
# Together with insufficient remaining space (never for the
|
|
@@ -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
|
|
|
@@ -63,10 +63,23 @@ module Idml
|
|
|
63
63
|
width = WrapContour.overlap_width(contour, line_y,
|
|
64
64
|
line_height, frame_x,
|
|
65
65
|
frame_right)
|
|
66
|
-
|
|
66
|
+
if contour.inverse
|
|
67
|
+
reduction = [reduction,
|
|
68
|
+
(frame_right - frame_x) - width].max
|
|
69
|
+
next
|
|
70
|
+
end
|
|
67
71
|
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
# Side-aware shapes (TODO 147): the side dispatch uses
|
|
73
|
+
# the polygon's bounding box; narrowing itself keeps the
|
|
74
|
+
# exact polygon overlap for BothSides.
|
|
75
|
+
bbox_left, _bottom, bbox_right, = WrapContour.bounding_box(
|
|
76
|
+
contour,
|
|
77
|
+
)
|
|
78
|
+
r, s = interval_side_adjustment(contour.side, bbox_left,
|
|
79
|
+
bbox_right, width,
|
|
80
|
+
frame_x, frame_right)
|
|
81
|
+
reduction = [reduction, r].max
|
|
82
|
+
shift = [shift, s].max
|
|
70
83
|
end
|
|
71
84
|
[reduction, shift]
|
|
72
85
|
end
|
|
@@ -86,34 +99,65 @@ module Idml
|
|
|
86
99
|
|
|
87
100
|
return [0.0, 0.0] if overlap.zero?
|
|
88
101
|
|
|
89
|
-
|
|
102
|
+
# JumpObject / NextColumn: text never flows beside the
|
|
103
|
+
# object — the full frame width is blocked and the renderer
|
|
104
|
+
# skips the run below the object's bottom edge.
|
|
105
|
+
return [frame_right - frame_x, 0.0] if contour.jump
|
|
106
|
+
|
|
107
|
+
interval_side_adjustment(contour.side, contour.x,
|
|
108
|
+
contour.x + contour.width, overlap,
|
|
109
|
+
frame_x, frame_right)
|
|
90
110
|
end
|
|
91
111
|
private :box_adjustment
|
|
92
112
|
|
|
93
|
-
|
|
94
|
-
|
|
113
|
+
# Bottom edge of the lowest jump contour overlapping the given
|
|
114
|
+
# band — where text resumes below a JumpObject. nil when no
|
|
115
|
+
# jump contour blocks the band.
|
|
116
|
+
def jump_contour_bottom(line_y, line_height, frame_x, frame_right)
|
|
117
|
+
bottoms = @contours.filter_map do |contour|
|
|
118
|
+
next unless box_contour?(contour) && contour.jump
|
|
119
|
+
next unless y_overlap?(contour, line_y, line_height) &&
|
|
120
|
+
x_overlap?(contour, frame_x, frame_right)
|
|
121
|
+
|
|
122
|
+
contour.y
|
|
123
|
+
end
|
|
124
|
+
bottoms.min
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def box_contour?(contour)
|
|
128
|
+
!contour.is_a?(WrapContour::Shape)
|
|
129
|
+
end
|
|
130
|
+
private :box_contour?
|
|
131
|
+
|
|
132
|
+
# Side dispatch over an object's horizontal extent
|
|
133
|
+
# [obj_left, obj_right] (TODO 138/147 — shared by box
|
|
134
|
+
# contours and Contour shapes' bounding boxes): LeftSide
|
|
135
|
+
# keeps text left of the object (reduce to its left edge, no
|
|
136
|
+
# shift); RightSide moves text past its right edge (reduce +
|
|
137
|
+
# shift); LargestArea picks the roomier side; BothSides and
|
|
138
|
+
# the spine variants reduce by `overlap` only.
|
|
139
|
+
def interval_side_adjustment(side, obj_left, obj_right, overlap,
|
|
140
|
+
frame_x, frame_right)
|
|
141
|
+
case side
|
|
95
142
|
when "LeftSide"
|
|
96
|
-
[frame_right -
|
|
143
|
+
[frame_right - obj_left, 0.0]
|
|
97
144
|
when "RightSide"
|
|
98
|
-
shift =
|
|
145
|
+
shift = obj_right - frame_x
|
|
99
146
|
[shift, shift]
|
|
100
147
|
when "LargestArea"
|
|
101
|
-
|
|
148
|
+
left_free = obj_left - frame_x
|
|
149
|
+
right_free = frame_right - obj_right
|
|
150
|
+
if left_free >= right_free
|
|
151
|
+
[frame_right - obj_left, 0.0]
|
|
152
|
+
else
|
|
153
|
+
shift = obj_right - frame_x
|
|
154
|
+
[shift, shift]
|
|
155
|
+
end
|
|
102
156
|
else
|
|
103
157
|
[overlap, 0.0]
|
|
104
158
|
end
|
|
105
159
|
end
|
|
106
|
-
private :
|
|
107
|
-
|
|
108
|
-
def largest_side_adjustment(contour, frame_x, frame_right)
|
|
109
|
-
left_free = contour.x - frame_x
|
|
110
|
-
right_free = frame_right - (contour.x + contour.width)
|
|
111
|
-
return [frame_right - contour.x, 0.0] if left_free >= right_free
|
|
112
|
-
|
|
113
|
-
shift = contour.x + contour.width - frame_x
|
|
114
|
-
[shift, shift]
|
|
115
|
-
end
|
|
116
|
-
private :largest_side_adjustment
|
|
160
|
+
private :interval_side_adjustment
|
|
117
161
|
|
|
118
162
|
# Returns the total horizontal overlap of all contours with a
|
|
119
163
|
# text line at the given y range within the given x range.
|
|
@@ -140,10 +184,11 @@ module Idml
|
|
|
140
184
|
# The wrap shape for an item: a bounding-box rectangle
|
|
141
185
|
# (BoundingBoxTextWrap — the legacy "BoundingBox" spelling is
|
|
142
186
|
# also accepted from early synthetic fixtures) or a flattened
|
|
143
|
-
# PathGeometry polygon (Contour mode). JumpObject
|
|
144
|
-
# NextColumn
|
|
145
|
-
#
|
|
146
|
-
#
|
|
187
|
+
# PathGeometry polygon (Contour mode). JumpObject moves text
|
|
188
|
+
# below the object; NextColumn approximates as the same
|
|
189
|
+
# jump-below in a single-column frame (true column jumping
|
|
190
|
+
# needs chain integration, documented in TODO.pdf/61).
|
|
191
|
+
JUMP_MODES = %w[JumpObjectTextWrap NextColumnTextWrap].freeze
|
|
147
192
|
def self.contour_for(item, page_height)
|
|
148
193
|
pref = wrap_preference(item)
|
|
149
194
|
return nil unless pref
|
|
@@ -165,7 +210,8 @@ module Idml
|
|
|
165
210
|
Contour.new(
|
|
166
211
|
x: rect[:x], y: rect[:y],
|
|
167
212
|
width: rect[:width], height: rect[:height],
|
|
168
|
-
side: pref&.text_wrap_side
|
|
213
|
+
side: pref&.text_wrap_side,
|
|
214
|
+
jump: JUMP_MODES.include?(pref&.text_wrap_mode)
|
|
169
215
|
)
|
|
170
216
|
end
|
|
171
217
|
private_class_method :box_contour
|
|
@@ -13,8 +13,9 @@ module Idml
|
|
|
13
13
|
|
|
14
14
|
# A wrap shape: flattened polygons (one per subpath) plus the
|
|
15
15
|
# TextWrapOffset bounds. `inverse` flips the wrap region:
|
|
16
|
-
# text may only flow INSIDE the shape.
|
|
17
|
-
|
|
16
|
+
# text may only flow INSIDE the shape. `side` carries
|
|
17
|
+
# TextWrapSide for side-aware narrowing (TODO 147).
|
|
18
|
+
Shape = Struct.new(:polygons, :offset, :inverse, :side,
|
|
18
19
|
keyword_init: true)
|
|
19
20
|
|
|
20
21
|
# Builds the wrap shape for an item's PathGeometry and wrap
|
|
@@ -24,7 +25,16 @@ module Idml
|
|
|
24
25
|
return nil if polygons.empty?
|
|
25
26
|
|
|
26
27
|
Shape.new(polygons: polygons, offset: wrap_offset(pref),
|
|
27
|
-
inverse: pref.inverse)
|
|
28
|
+
inverse: pref.inverse, side: pref.text_wrap_side)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The shape's bounding box: [left, bottom, right, top] in
|
|
32
|
+
# PDF coordinates, flattened across subpaths.
|
|
33
|
+
def self.bounding_box(shape)
|
|
34
|
+
points = shape.polygons.flatten(1)
|
|
35
|
+
xs = points.map(&:first)
|
|
36
|
+
ys = points.map(&:last)
|
|
37
|
+
[xs.min, ys.min, xs.max, ys.max]
|
|
28
38
|
end
|
|
29
39
|
|
|
30
40
|
def self.polygons_for(item, page_height)
|
|
@@ -10,22 +10,95 @@ module Idml
|
|
|
10
10
|
# Mojikumi subset: automatic inter-script spacing — an eighth
|
|
11
11
|
# em is inserted between adjacent CJK and ASCII alphanumeric
|
|
12
12
|
# glyphs (widenning the leading glyph of each pair), matching
|
|
13
|
-
# InDesign's default CJK/Latin auto spacing.
|
|
13
|
+
# InDesign's default CJK/Latin auto spacing. A named
|
|
14
|
+
# mojikumi set's OverrideMojikumiAki entries take precedence
|
|
15
|
+
# when given: the Desired value (em units) applies to each
|
|
16
|
+
# matching class pair (TODO 145).
|
|
14
17
|
SCRIPT_SPACING_EM = 0.125
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
# Mojikumi character classes (InDesign mojikumi table rows).
|
|
20
|
+
CLASS_IDEOGRAPH = 1
|
|
21
|
+
CLASS_OPENING = 2
|
|
22
|
+
CLASS_CLOSING = 3
|
|
23
|
+
CLASS_COMMA_PERIOD = 4
|
|
24
|
+
CLASS_MIDDLE_DOT = 5
|
|
25
|
+
CLASS_DIGIT = 6
|
|
26
|
+
CLASS_LATIN = 7
|
|
27
|
+
|
|
28
|
+
COMMA_PERIOD_CODEPOINTS = [
|
|
29
|
+
0x3001, # 、 ideographic comma
|
|
30
|
+
0x3002, # 。 ideographic full stop
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
CLOSING_CODEPOINTS = [
|
|
34
|
+
0x3009, # 〉
|
|
35
|
+
0x300B, # 》
|
|
36
|
+
0x300D, # 」
|
|
37
|
+
0x300F, # 』
|
|
38
|
+
0x3011, # 】
|
|
39
|
+
0x3015, # 〕
|
|
40
|
+
0xFF09, # )fullwidth right paren
|
|
41
|
+
0xFF3D, # ]
|
|
42
|
+
0xFF5D, # }
|
|
43
|
+
].freeze
|
|
44
|
+
|
|
45
|
+
def apply_script_spacing(glyphs, size, aki_overrides = [])
|
|
18
46
|
(0...(glyphs.length - 1)).each do |index|
|
|
19
47
|
leading = glyphs[index]
|
|
20
48
|
following = glyphs[index + 1]
|
|
21
|
-
|
|
22
|
-
|
|
49
|
+
em = aki_em(leading.codepoint, following.codepoint,
|
|
50
|
+
aki_overrides)
|
|
51
|
+
next unless em&.positive?
|
|
23
52
|
|
|
24
|
-
leading.width +=
|
|
53
|
+
leading.width += em * size
|
|
25
54
|
end
|
|
26
55
|
glyphs
|
|
27
56
|
end
|
|
28
57
|
|
|
58
|
+
# Spacing (em units) between an adjacent pair: the named
|
|
59
|
+
# set's Desired aki for a matching class-pair override, else
|
|
60
|
+
# the default eighth-em at CJK/Latin script boundaries, else
|
|
61
|
+
# none.
|
|
62
|
+
def aki_em(left, right, aki_overrides)
|
|
63
|
+
override = aki_overrides.find do |entry|
|
|
64
|
+
entry.side_is_after_target &&
|
|
65
|
+
entry.target_mojikumi_class == mojikumi_class(left) &&
|
|
66
|
+
entry.side_mojikumi_class == mojikumi_class(right)
|
|
67
|
+
end
|
|
68
|
+
return override.desired if override&.desired
|
|
69
|
+
|
|
70
|
+
return SCRIPT_SPACING_EM if script_boundary?(left, right)
|
|
71
|
+
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# The mojikumi class of a codepoint, or nil outside the
|
|
76
|
+
# CJK punctuation / script classes. Ideographs (the default
|
|
77
|
+
# CJK class) win only when no special class matches.
|
|
78
|
+
def mojikumi_class(codepoint)
|
|
79
|
+
return CLASS_OPENING if OPENING_CODEPOINTS.include?(codepoint)
|
|
80
|
+
return CLASS_COMMA_PERIOD if COMMA_PERIOD_CODEPOINTS.include?(codepoint)
|
|
81
|
+
return CLASS_CLOSING if CLOSING_CODEPOINTS.include?(codepoint)
|
|
82
|
+
return CLASS_MIDDLE_DOT if MIDDLE_CODEPOINTS.include?(codepoint)
|
|
83
|
+
return CLASS_DIGIT if digit_class?(codepoint)
|
|
84
|
+
return CLASS_LATIN if latin_class?(codepoint)
|
|
85
|
+
return CLASS_IDEOGRAPH if cjk?(codepoint)
|
|
86
|
+
|
|
87
|
+
nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def digit_class?(codepoint)
|
|
91
|
+
(0x30..0x39).cover?(codepoint) ||
|
|
92
|
+
(0xFF10..0xFF19).cover?(codepoint)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def latin_class?(codepoint)
|
|
96
|
+
(0x41..0x5A).cover?(codepoint) ||
|
|
97
|
+
(0x61..0x7A).cover?(codepoint) ||
|
|
98
|
+
(0xFF21..0xFF3A).cover?(codepoint) ||
|
|
99
|
+
(0xFF41..0xFF5A).cover?(codepoint)
|
|
100
|
+
end
|
|
101
|
+
|
|
29
102
|
# True when exactly one of the pair is CJK and the other is
|
|
30
103
|
# an ASCII letter or digit.
|
|
31
104
|
def script_boundary?(left, right)
|
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.10.
|
|
4
|
+
version: 0.10.9
|
|
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-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bigdecimal
|
|
@@ -205,6 +205,12 @@ 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
|
|
211
|
+
- TODO.pdf/145-apply-mojikumi-aki.md
|
|
212
|
+
- TODO.pdf/146-next-column-wrap.md
|
|
213
|
+
- TODO.pdf/147-side-aware-shapes.md
|
|
208
214
|
- TODO.pdf/15-wire-spread-children.md
|
|
209
215
|
- TODO.pdf/16-typed-model-pipeline.md
|
|
210
216
|
- TODO.pdf/17-shape-rendering.md
|
|
@@ -348,6 +354,7 @@ files:
|
|
|
348
354
|
- lib/idml/elements/master_spread_object.rb
|
|
349
355
|
- lib/idml/elements/mixed_ink.rb
|
|
350
356
|
- lib/idml/elements/mixed_ink_group.rb
|
|
357
|
+
- lib/idml/elements/mojikumi_table.rb
|
|
351
358
|
- lib/idml/elements/object_export_option.rb
|
|
352
359
|
- lib/idml/elements/object_style.rb
|
|
353
360
|
- lib/idml/elements/object_style_content_effects_category_settings.rb
|