idml 0.9.8 → 0.10.0
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/127-keep-with-next.md +20 -0
- data/TODO.pdf/128-first-baseline-offset.md +21 -0
- data/TODO.pdf/129-justification-glyph-scaling.md +18 -0
- data/TODO.pdf/61-known-limitations.md +45 -26
- data/lib/idml/render/footnote.rb +1 -0
- data/lib/idml/render/renderers/text_frame_renderer.rb +107 -9
- data/lib/idml/render/style_resolver.rb +10 -1
- data/lib/idml/text_engine/justifier.rb +26 -2
- data/lib/idml/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 13770ed8937eeed41a40bd223bb8caadc833da870858d691caa0e1806058c286
|
|
4
|
+
data.tar.gz: dec0fe8f24c1aaa8258ecf18506560b1e0793bd7d4ebe56bf361dc1a69ff736a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0394efac9722c854cd4e8c2771d0ec494aaa8d3b50ca6ea471bbe8f5f44daf62a1f1bd4d12031ee9d59b0d7b6c5f65f5378db661d5a110d056734958369a870e'
|
|
7
|
+
data.tar.gz: 07d3a9492ea5a41819f4fe0353176671307ad7263feedeb909bd08f8284eb79b84df0fef0057db1d089a3a02ff7d255459ea2e232e5c929d3d1ffccc70685e81
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# TODO PDF 127: KeepWithNext
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE — implemented 2026-08-20
|
|
4
|
+
|
|
5
|
+
## What was done
|
|
6
|
+
|
|
7
|
+
- `StyleResolver::Paragraph` carries `keep_with_next` from the PSR.
|
|
8
|
+
- A paragraph with KeepWithNext defers to the next frame when the
|
|
9
|
+
FOLLOWING paragraph is forced there (StartParagraph break) or
|
|
10
|
+
when the follower's first line would not fit after this
|
|
11
|
+
paragraph (pre-measured via TextEngine::Measurement plus the
|
|
12
|
+
follower's leading). Combined with TODO 123's KeepAllLinesTogether,
|
|
13
|
+
both keep flavors share one deferral predicate in
|
|
14
|
+
`consume_paragraphs`.
|
|
15
|
+
|
|
16
|
+
## Known limitations
|
|
17
|
+
|
|
18
|
+
- The numeric strength of KeepWithNext (1–5, lines to keep) is
|
|
19
|
+
treated as boolean.
|
|
20
|
+
- Not applied in vertical writing mode.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# TODO PDF 128: Text frame FirstBaselineOffset
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE — implemented 2026-08-23
|
|
4
|
+
|
|
5
|
+
## What was done
|
|
6
|
+
|
|
7
|
+
`TextFrameRenderer` honors
|
|
8
|
+
`TextFramePreference.FirstBaselineOffset` when positioning the
|
|
9
|
+
first line's baseline: AscentOffset places it ascent-below the top
|
|
10
|
+
inset (typically higher than the default leading-based position);
|
|
11
|
+
FixedHeight uses MinimumFirstBaselineOffset. LeadingOffset /
|
|
12
|
+
CapHeight / XHeight / EmboxHeight approximate as the existing
|
|
13
|
+
leading-based default (cap/x-height metrics are not exposed by
|
|
14
|
+
PdfrbFontMetrics). Applied in both the single-column and
|
|
15
|
+
multi-column paths.
|
|
16
|
+
|
|
17
|
+
## Known limitations
|
|
18
|
+
|
|
19
|
+
- CapHeight / XHeight offsets fall back to leading (font metrics
|
|
20
|
+
expose ascent only).
|
|
21
|
+
- Not applied in vertical writing mode.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# TODO PDF 129: Justification glyph scaling
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE — implemented 2026-08-23
|
|
4
|
+
|
|
5
|
+
## What was done
|
|
6
|
+
|
|
7
|
+
`Justifier` distributes any residual slack remaining after the
|
|
8
|
+
word-spacing and letter-spacing caps as uniform glyph scaling,
|
|
9
|
+
capped at `MaximumGlyphScaling` (PSR → Paragraph → SpacingLimits).
|
|
10
|
+
InDesign's default cap (100%) disables scaling — only documents
|
|
11
|
+
that relax the cap get stretched glyphs, matching TODO 119's
|
|
12
|
+
layered distribution: words first, then letters, then glyphs.
|
|
13
|
+
|
|
14
|
+
## Known limitations
|
|
15
|
+
|
|
16
|
+
- MinimumGlyphScaling (compression of overlong lines) is not
|
|
17
|
+
applied — the breaker never produces overlong justified lines
|
|
18
|
+
except forced unbreakable runs.
|
|
@@ -1,35 +1,54 @@
|
|
|
1
1
|
# TODO PDF 61: Known limitations and future work
|
|
2
2
|
|
|
3
|
-
## Status: DOCUMENTED
|
|
3
|
+
## Status: DOCUMENTED — refreshed 2026-08-20
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
not embed FontFile2 binary data in the PDF. Text renders correctly
|
|
9
|
-
on systems with the font installed but may not render on other
|
|
10
|
-
systems. Requires pdfrb font subsetting support (TODO 52).
|
|
5
|
+
Previously listed limitations (font embedding, tagged PDF, per-run
|
|
6
|
+
styling, dead code) were resolved by TODOs 52/53/55/67/76 long ago;
|
|
7
|
+
this list reflects the current state.
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
JPEG at a macOS-specific path. Tests that check image embedding
|
|
14
|
-
are skipped on CI. A self-contained fixture with an embedded image
|
|
15
|
-
would fix this.
|
|
16
|
-
|
|
17
|
-
3. **Text engine integration**: TextFrameRenderer uses the text engine
|
|
18
|
-
for line breaking when FontMetrics is available (Helvetica as .ttf).
|
|
19
|
-
On macOS, Helvetica is a .ttc collection, so the fallback simple
|
|
20
|
-
renderer is used (no line breaking). Per-run styling (different
|
|
21
|
-
fonts/sizes in one frame) is simplified to use the first run's style.
|
|
9
|
+
## Known limitations
|
|
22
10
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
11
|
+
### Text layout
|
|
12
|
+
- **Hyphenation** is not implemented (Hyphenation* attributes are
|
|
13
|
+
parsed but unused); a hyphenation dictionary would be required.
|
|
14
|
+
- **Justification**: word/letter spacing caps apply (TODO 119) and
|
|
15
|
+
MaximumGlyphScaling stretches as the last resort (TODO 129);
|
|
16
|
+
MinimumGlyphScaling compression and ToBinding / RTL binding
|
|
17
|
+
alignment are not applied.
|
|
18
|
+
- **Keep options**: whole-paragraph KeepAllLinesTogether (TODO
|
|
19
|
+
123) and KeepWithNext (TODO 127) are honored; partial keep
|
|
20
|
+
windows (KeepFirstLines / KeepLastLines) are not.
|
|
21
|
+
- **First baseline**: AscentOffset and FixedHeight are honored
|
|
22
|
+
(TODO 128); CapHeight / XHeight approximate as leading.
|
|
23
|
+
- **StartParagraph** breaks act at frame/column granularity — no
|
|
24
|
+
odd/even page parity.
|
|
25
|
+
- **Text wrap** (TODO 114): BoundingBox mode only; Shape contour
|
|
26
|
+
following and Inverse mode are not implemented.
|
|
27
|
+
|
|
28
|
+
### CJK
|
|
29
|
+
- **Mojikumi**: CJK/Latin auto script spacing is applied (TODO
|
|
30
|
+
125); full class-based punctuation compression tables are not.
|
|
31
|
+
- **Vertical mode**: Latin rotation is per glyph (not run-grouped);
|
|
32
|
+
ruby placement is beside-column; keep options do not apply.
|
|
33
|
+
|
|
34
|
+
### Structural features
|
|
35
|
+
- **Endnotes** (TODO 117): reference markers render; endnote TEXT
|
|
36
|
+
rendering is gated on a real fixture (the EndnoteTextRange
|
|
37
|
+
reference chain is opaque without one).
|
|
38
|
+
- **Footnotes** number per story; overflow balancing (NoSplitting)
|
|
39
|
+
is not modeled; the simple-render fallback shows markers without
|
|
40
|
+
footnote text.
|
|
41
|
+
- **Anchored objects** render at stored geometry; AnchorType
|
|
42
|
+
text-reflow is not simulated.
|
|
43
|
+
- **Tables** do not span frames (no repeated header rows); diagonal
|
|
44
|
+
cell strokes are modeled but not drawn.
|
|
45
|
+
- **Inline anchored objects / footnotes inside table cells** are
|
|
46
|
+
not handled.
|
|
27
47
|
|
|
28
48
|
## Future enhancements
|
|
29
49
|
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
- Per-run text styling with cursor tracking
|
|
50
|
+
- Hyphenation dictionary integration
|
|
51
|
+
- Shape-mode text wrap contours
|
|
52
|
+
- Frame-spanning tables with header repeats
|
|
53
|
+
- Class-based mojikumi tables
|
|
35
54
|
- Performance benchmark for 100+ page documents
|
data/lib/idml/render/footnote.rb
CHANGED
|
@@ -131,6 +131,7 @@ module Idml
|
|
|
131
131
|
limits = TextEngine::Justifier::SpacingLimits.new(
|
|
132
132
|
max_word_spacing: paragraph.maximum_word_spacing,
|
|
133
133
|
max_letter_spacing: paragraph.maximum_letter_spacing,
|
|
134
|
+
max_glyph_scaling: paragraph.maximum_glyph_scaling,
|
|
134
135
|
)
|
|
135
136
|
lines.each_with_index do |line, index|
|
|
136
137
|
TextEngine::Justifier.justify(
|
|
@@ -433,6 +433,7 @@ module Idml
|
|
|
433
433
|
bottom_limit = TextEngine::VerticalLayout.bottom_limit(layout_frame)
|
|
434
434
|
footnotes = []
|
|
435
435
|
cursor_y = vertical_justify_top(top_y, bottom_limit, state, context)
|
|
436
|
+
cursor_y -= first_baseline_offset(context, state, font)
|
|
436
437
|
char_cursor = state.char_cursor
|
|
437
438
|
|
|
438
439
|
if state.current_paragraph
|
|
@@ -473,7 +474,7 @@ module Idml
|
|
|
473
474
|
top_y = col_frame.y + col_frame.height - (col_frame.inset_top || 0)
|
|
474
475
|
bottom_limit = TextEngine::VerticalLayout.bottom_limit(col_frame)
|
|
475
476
|
footnotes = []
|
|
476
|
-
cursor_y = top_y
|
|
477
|
+
cursor_y = top_y - first_baseline_offset(context, state, font)
|
|
477
478
|
char_cursor = state.char_cursor
|
|
478
479
|
|
|
479
480
|
if state.current_paragraph
|
|
@@ -564,6 +565,53 @@ module Idml
|
|
|
564
565
|
end
|
|
565
566
|
private_class_method :vertical_justify_top
|
|
566
567
|
|
|
568
|
+
# FirstBaselineOffset: how far below the frame's top inset
|
|
569
|
+
# the first line's baseline sits. The layout's default is
|
|
570
|
+
# leading-based; AscentOffset shifts by (ascent − leading)
|
|
571
|
+
# and FixedHeight by (MinimumFirstBaselineOffset − leading).
|
|
572
|
+
# CapHeight / XHeight / EmboxHeight approximate as leading.
|
|
573
|
+
# Returns the extra shift to subtract from the cursor.
|
|
574
|
+
def self.first_baseline_offset(context, state, font)
|
|
575
|
+
pref = context.item&.text_frame_preference&.first
|
|
576
|
+
mode = pref&.first_baseline_offset
|
|
577
|
+
return 0.0 unless %w[AscentOffset FixedHeight].include?(mode)
|
|
578
|
+
|
|
579
|
+
baseline_target(pref, mode, state, font) -
|
|
580
|
+
first_paragraph_leading(state)
|
|
581
|
+
end
|
|
582
|
+
private_class_method :first_baseline_offset
|
|
583
|
+
|
|
584
|
+
# Distance from the top inset to the first baseline under
|
|
585
|
+
# the given mode.
|
|
586
|
+
def self.baseline_target(pref, mode, state, font)
|
|
587
|
+
if mode == "AscentOffset"
|
|
588
|
+
ascent_scaled(font) * first_paragraph_leading(state)
|
|
589
|
+
else
|
|
590
|
+
pref.minimum_first_baseline_offset ||
|
|
591
|
+
first_paragraph_leading(state)
|
|
592
|
+
end
|
|
593
|
+
end
|
|
594
|
+
private_class_method :baseline_target
|
|
595
|
+
|
|
596
|
+
def self.ascent_scaled(font)
|
|
597
|
+
upem = font.units_per_em.zero? ? 1 : font.units_per_em
|
|
598
|
+
font.ascent / upem
|
|
599
|
+
end
|
|
600
|
+
private_class_method :ascent_scaled
|
|
601
|
+
|
|
602
|
+
def self.first_paragraph_leading(state)
|
|
603
|
+
paragraph = state.current_paragraph || state.paragraphs.first
|
|
604
|
+
unless paragraph
|
|
605
|
+
return DEFAULT_SIZE *
|
|
606
|
+
TextEngine::VerticalLayout::DEFAULT_LEADING_FACTOR
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
size = paragraph.runs.first&.point_size || DEFAULT_SIZE
|
|
610
|
+
TextEngine::VerticalLayout.leading_for(paragraph.auto_leading,
|
|
611
|
+
size)
|
|
612
|
+
end
|
|
613
|
+
private_class_method :first_paragraph_leading
|
|
614
|
+
|
|
567
615
|
def self.text_frame_vertical_justification(context)
|
|
568
616
|
pref = context.item&.text_frame_preference&.first
|
|
569
617
|
pref&.vertical_justification
|
|
@@ -629,11 +677,13 @@ module Idml
|
|
|
629
677
|
cursor_y, bottom_limit, char_cursor,
|
|
630
678
|
footnotes)
|
|
631
679
|
pending = 0
|
|
632
|
-
state.paragraphs.
|
|
680
|
+
state.paragraphs.each_with_index do |paragraph, index|
|
|
633
681
|
break if cursor_y < bottom_limit
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
682
|
+
|
|
683
|
+
next_paragraph = state.paragraphs[index + 1]
|
|
684
|
+
break if paragraph_deferred?(paragraph, next_paragraph,
|
|
685
|
+
layout_frame, font, cursor_y,
|
|
686
|
+
bottom_limit, pending.positive?)
|
|
637
687
|
|
|
638
688
|
remaining_runs, cursor_y, char_cursor = render_runs_for_paragraph(
|
|
639
689
|
canvas, paragraph, paragraph.runs, context,
|
|
@@ -969,11 +1019,25 @@ module Idml
|
|
|
969
1019
|
# frame: a StartParagraph forced break, or KeepAllLines-
|
|
970
1020
|
# Together with insufficient remaining space (never for the
|
|
971
1021
|
# frame's first paragraph, so progress is always made).
|
|
972
|
-
def self.paragraph_deferred?(paragraph,
|
|
973
|
-
|
|
1022
|
+
def self.paragraph_deferred?(paragraph, next_paragraph,
|
|
1023
|
+
layout_frame, font, cursor_y,
|
|
1024
|
+
bottom_limit, placed_any)
|
|
974
1025
|
return true if paragraph_break?(paragraph) && placed_any
|
|
975
|
-
return false unless paragraph.keep_all_lines_together
|
|
976
1026
|
return false unless placed_any
|
|
1027
|
+
return true if keep_with_next_break?(paragraph, next_paragraph,
|
|
1028
|
+
layout_frame, font,
|
|
1029
|
+
cursor_y, bottom_limit)
|
|
1030
|
+
|
|
1031
|
+
keep_all_lines_break?(paragraph, layout_frame, font,
|
|
1032
|
+
cursor_y, bottom_limit)
|
|
1033
|
+
end
|
|
1034
|
+
private_class_method :paragraph_deferred?
|
|
1035
|
+
|
|
1036
|
+
# KeepAllLinesTogether: defer when the paragraph cannot
|
|
1037
|
+
# fully fit the remaining space.
|
|
1038
|
+
def self.keep_all_lines_break?(paragraph, layout_frame, font,
|
|
1039
|
+
cursor_y, bottom_limit)
|
|
1040
|
+
return false unless paragraph.keep_all_lines_together
|
|
977
1041
|
|
|
978
1042
|
wrap_width = TextEngine::VerticalLayout.wrap_width(
|
|
979
1043
|
layout_frame, paragraph.right_indent || 0
|
|
@@ -983,7 +1047,40 @@ module Idml
|
|
|
983
1047
|
)
|
|
984
1048
|
(cursor_y - height) < bottom_limit
|
|
985
1049
|
end
|
|
986
|
-
private_class_method :
|
|
1050
|
+
private_class_method :keep_all_lines_break?
|
|
1051
|
+
|
|
1052
|
+
# KeepWithNext: this paragraph defers when the next
|
|
1053
|
+
# paragraph is forced to the next frame, or when the next
|
|
1054
|
+
# paragraph's first line would not fit after this one.
|
|
1055
|
+
def self.keep_with_next_break?(paragraph, next_paragraph,
|
|
1056
|
+
layout_frame, font, cursor_y,
|
|
1057
|
+
bottom_limit)
|
|
1058
|
+
return false unless keepable_pair?(paragraph, next_paragraph)
|
|
1059
|
+
return true if paragraph_break?(next_paragraph)
|
|
1060
|
+
|
|
1061
|
+
wrap_width = TextEngine::VerticalLayout.wrap_width(
|
|
1062
|
+
layout_frame, paragraph.right_indent || 0
|
|
1063
|
+
)
|
|
1064
|
+
height = TextEngine::Measurement.paragraph_height(
|
|
1065
|
+
paragraph, font, wrap_width
|
|
1066
|
+
)
|
|
1067
|
+
(cursor_y - height - follower_leading(next_paragraph)) <
|
|
1068
|
+
bottom_limit
|
|
1069
|
+
end
|
|
1070
|
+
private_class_method :keep_with_next_break?
|
|
1071
|
+
|
|
1072
|
+
def self.keepable_pair?(paragraph, next_paragraph)
|
|
1073
|
+
!paragraph.keep_with_next.nil? && !next_paragraph.nil?
|
|
1074
|
+
end
|
|
1075
|
+
private_class_method :keepable_pair?
|
|
1076
|
+
|
|
1077
|
+
def self.follower_leading(next_paragraph)
|
|
1078
|
+
size = next_paragraph.runs.first&.point_size || DEFAULT_SIZE
|
|
1079
|
+
TextEngine::VerticalLayout.leading_for(
|
|
1080
|
+
next_paragraph.auto_leading, size
|
|
1081
|
+
)
|
|
1082
|
+
end
|
|
1083
|
+
private_class_method :follower_leading
|
|
987
1084
|
|
|
988
1085
|
# True when the paragraph requests a forced break to the
|
|
989
1086
|
# next frame/column (StartParagraph). All break flavors act
|
|
@@ -1002,6 +1099,7 @@ module Idml
|
|
|
1002
1099
|
limits = TextEngine::Justifier::SpacingLimits.new(
|
|
1003
1100
|
max_word_spacing: paragraph.maximum_word_spacing,
|
|
1004
1101
|
max_letter_spacing: paragraph.maximum_letter_spacing,
|
|
1102
|
+
max_glyph_scaling: paragraph.maximum_glyph_scaling,
|
|
1005
1103
|
)
|
|
1006
1104
|
lines.each_with_index do |line, index|
|
|
1007
1105
|
TextEngine::Justifier.justify(
|
|
@@ -68,12 +68,16 @@ module Idml
|
|
|
68
68
|
# Justification distribution caps (percent; from PSR).
|
|
69
69
|
:maximum_word_spacing,
|
|
70
70
|
:maximum_letter_spacing,
|
|
71
|
+
:maximum_glyph_scaling,
|
|
71
72
|
# Forced paragraph break (StartParagraph: NextPage /
|
|
72
73
|
# NextColumn / NextFrame / NextOddPage / NextEvenPage).
|
|
73
74
|
:start_paragraph,
|
|
74
75
|
# Widow/orphan control: KeepAllLinesTogether pushes the whole
|
|
75
|
-
# paragraph to the next frame when it cannot fully fit
|
|
76
|
+
# paragraph to the next frame when it cannot fully fit;
|
|
77
|
+
# KeepWithNext binds this paragraph to the next paragraph's
|
|
78
|
+
# first line.
|
|
76
79
|
:keep_all_lines_together,
|
|
80
|
+
:keep_with_next,
|
|
77
81
|
:bullets_and_numbering_list_type,
|
|
78
82
|
:bullet_character_value,
|
|
79
83
|
:bullets_text_after,
|
|
@@ -202,11 +206,16 @@ module Idml
|
|
|
202
206
|
maximum_letter_spacing: resolve_attr(
|
|
203
207
|
style_lookup, psr, :maximum_letter_spacing
|
|
204
208
|
),
|
|
209
|
+
maximum_glyph_scaling: resolve_attr(
|
|
210
|
+
style_lookup, psr, :maximum_glyph_scaling
|
|
211
|
+
),
|
|
205
212
|
start_paragraph: resolve_attr(style_lookup, psr,
|
|
206
213
|
:start_paragraph),
|
|
207
214
|
keep_all_lines_together: resolve_attr(
|
|
208
215
|
style_lookup, psr, :keep_all_lines_together
|
|
209
216
|
),
|
|
217
|
+
keep_with_next: resolve_attr(style_lookup, psr,
|
|
218
|
+
:keep_with_next),
|
|
210
219
|
drop_cap_lines: resolve_attr(style_lookup, psr, :drop_cap_lines),
|
|
211
220
|
drop_cap_characters: resolve_attr(style_lookup, psr,
|
|
212
221
|
:drop_cap_characters),
|
|
@@ -10,11 +10,13 @@ module Idml
|
|
|
10
10
|
# Justification distribution caps, in percent (100 = natural).
|
|
11
11
|
# InDesign defaults: word spacing max 133%, letter spacing 0%.
|
|
12
12
|
SpacingLimits = Struct.new(
|
|
13
|
-
:max_word_spacing, :max_letter_spacing,
|
|
13
|
+
:max_word_spacing, :max_letter_spacing, :max_glyph_scaling,
|
|
14
|
+
keyword_init: true
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
DEFAULT_MAX_WORD_SPACING = 133.0
|
|
17
18
|
DEFAULT_MAX_LETTER_SPACING = 0.0
|
|
19
|
+
DEFAULT_MAX_GLYPH_SCALING = 100.0
|
|
18
20
|
|
|
19
21
|
def self.justify(line:, frame_width:, alignment: :left,
|
|
20
22
|
last_line: false, limits: nil)
|
|
@@ -63,7 +65,12 @@ module Idml
|
|
|
63
65
|
glyph.width += extra
|
|
64
66
|
end
|
|
65
67
|
residual = slack - word_share
|
|
66
|
-
|
|
68
|
+
return unless residual.positive?
|
|
69
|
+
|
|
70
|
+
before = line.glyphs.sum(&:width)
|
|
71
|
+
distribute_letter_spacing(line, residual)
|
|
72
|
+
residual -= (line.glyphs.sum(&:width) - before)
|
|
73
|
+
distribute_glyph_scaling(line, residual) if residual.positive?
|
|
67
74
|
end
|
|
68
75
|
|
|
69
76
|
# Total room the word spaces may grow within the max-word-
|
|
@@ -91,6 +98,23 @@ module Idml
|
|
|
91
98
|
line.glyphs.each { |glyph| glyph.width += add }
|
|
92
99
|
end
|
|
93
100
|
private :distribute_letter_spacing
|
|
101
|
+
|
|
102
|
+
# Last resort: uniform glyph scaling for any remaining slack,
|
|
103
|
+
# capped at max_glyph_scaling percent. InDesign's default cap
|
|
104
|
+
# (100%) disables it; only documents that relax the cap get
|
|
105
|
+
# stretched glyphs.
|
|
106
|
+
def distribute_glyph_scaling(line, residual)
|
|
107
|
+
max_pct = @limits&.max_glyph_scaling || DEFAULT_MAX_GLYPH_SCALING
|
|
108
|
+
grow = (max_pct - 100) / 100.0
|
|
109
|
+
return unless grow.positive?
|
|
110
|
+
|
|
111
|
+
total = line.glyphs.sum(&:width)
|
|
112
|
+
return unless total.positive?
|
|
113
|
+
|
|
114
|
+
factor = [residual / total, grow].min
|
|
115
|
+
line.glyphs.each { |glyph| glyph.width *= (1 + factor) }
|
|
116
|
+
end
|
|
117
|
+
private :distribute_glyph_scaling
|
|
94
118
|
end
|
|
95
119
|
end
|
|
96
120
|
end
|
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.
|
|
4
|
+
version: 0.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose
|
|
@@ -185,6 +185,9 @@ files:
|
|
|
185
185
|
- TODO.pdf/124-tatechuyoko-vertical.md
|
|
186
186
|
- TODO.pdf/125-script-spacing.md
|
|
187
187
|
- TODO.pdf/126-part-cache.md
|
|
188
|
+
- TODO.pdf/127-keep-with-next.md
|
|
189
|
+
- TODO.pdf/128-first-baseline-offset.md
|
|
190
|
+
- TODO.pdf/129-justification-glyph-scaling.md
|
|
188
191
|
- TODO.pdf/13-cjk-text-layout.md
|
|
189
192
|
- TODO.pdf/14-page-item-element-models.md
|
|
190
193
|
- TODO.pdf/15-wire-spread-children.md
|