idml 0.9.8 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 29f97721ebdd63304b8c879eaa45764906dfa40b85363688aa9d860ccdd6f62c
4
- data.tar.gz: 439a3e3454be081957617ce5107ae735a47d56c4dc206104d3e483c5a20db669
3
+ metadata.gz: fd4031c47fca99598c64ed757ac446d3e1f2d2870603eb31af338132dae01288
4
+ data.tar.gz: 520a5ded7c74cfc50d0f893921486930c295b113f7e0d3c7af4dfb576186498c
5
5
  SHA512:
6
- metadata.gz: 0c669bcf817d1e2b8ab74c116e3a0e472b8ca5529fdfeba4f0adb85d7f1ddb78da5d422c63907cbe68f4a7c381ce5862d17b674063070bce64e9664584159b97
7
- data.tar.gz: b427dc2a77703a71074fc541eb8eea36485dc78607dea9faa8f13d3ca69c70e013a749374ef8abca81057bdf5e7563f6717bea30e45ad967a5836ead3e325d6a
6
+ metadata.gz: bad5a41e54b9e173241e08ba9e7006e6f621230ff782e89e4821d6b26749ee11b30fe8a38dae5185ee1cb41e4497ec20f32267584852cac43c9a37e41f488990
7
+ data.tar.gz: a62b908ae372636a3dec1722265ee44b24ab847510b348dd0029a3d04b388e41c6ad708f53c11ba21d7077817cfa831d700626ee73cc0380e46bfcb02e7ea58d
@@ -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.
@@ -1,35 +1,51 @@
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
- ## Known limitations
6
-
7
- 1. **Font embedding**: pdfrb 0.3.0 registers fonts by name but does
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
- 2. **Image fixture tests**: The fixture IDML references an external
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
- 4. **Dead code**: Old modules (PdfWriter, FontEmbedder, Color, Path,
24
- Text) are marked DEPRECATED but cannot be deleted per the project's
25
- "never delete source files" policy. They are not used by any active
26
- code path.
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);
15
+ glyph scaling (Min/Max/DesiredGlyphScaling) is not applied;
16
+ ToBinding / RTL binding alignment maps to left.
17
+ - **Keep options**: whole-paragraph KeepAllLinesTogether and
18
+ KeepWithNext(first-line) are honored; partial keep windows
19
+ (KeepFirstLines / KeepLastLines) are not.
20
+ - **StartParagraph** breaks act at frame/column granularity — no
21
+ odd/even page parity.
22
+ - **Text wrap** (TODO 114): BoundingBox mode only; Shape contour
23
+ following and Inverse mode are not implemented.
24
+
25
+ ### CJK
26
+ - **Mojikumi**: CJK/Latin auto script spacing is applied (TODO
27
+ 125); full class-based punctuation compression tables are not.
28
+ - **Vertical mode**: Latin rotation is per glyph (not run-grouped);
29
+ ruby placement is beside-column; keep options do not apply.
30
+
31
+ ### Structural features
32
+ - **Endnotes** (TODO 117): reference markers render; endnote TEXT
33
+ rendering is gated on a real fixture (the EndnoteTextRange
34
+ reference chain is opaque without one).
35
+ - **Footnotes** number per story; overflow balancing (NoSplitting)
36
+ is not modeled; the simple-render fallback shows markers without
37
+ footnote text.
38
+ - **Anchored objects** render at stored geometry; AnchorType
39
+ text-reflow is not simulated.
40
+ - **Tables** do not span frames (no repeated header rows); diagonal
41
+ cell strokes are modeled but not drawn.
42
+ - **Inline anchored objects / footnotes inside table cells** are
43
+ not handled.
27
44
 
28
45
  ## Future enhancements
29
46
 
30
- - Font subsetting (TODO 52) — requires pdfrb enhancement
31
- - Tagged PDF / PDF/UA (TODO 53) — requires pdfrb StructTreeRoot
32
- - Dead code removal (TODO 51) — when policy allows
33
- - Self-contained test fixture with embedded image
34
- - Per-run text styling with cursor tracking
47
+ - Hyphenation dictionary integration
48
+ - Shape-mode text wrap contours
49
+ - Frame-spanning tables with header repeats
50
+ - Class-based mojikumi tables
35
51
  - Performance benchmark for 100+ page documents
@@ -629,11 +629,13 @@ module Idml
629
629
  cursor_y, bottom_limit, char_cursor,
630
630
  footnotes)
631
631
  pending = 0
632
- state.paragraphs.each do |paragraph|
632
+ state.paragraphs.each_with_index do |paragraph, index|
633
633
  break if cursor_y < bottom_limit
634
- break if paragraph_deferred?(paragraph, layout_frame, font,
635
- cursor_y, bottom_limit,
636
- pending.positive?)
634
+
635
+ next_paragraph = state.paragraphs[index + 1]
636
+ break if paragraph_deferred?(paragraph, next_paragraph,
637
+ layout_frame, font, cursor_y,
638
+ bottom_limit, pending.positive?)
637
639
 
638
640
  remaining_runs, cursor_y, char_cursor = render_runs_for_paragraph(
639
641
  canvas, paragraph, paragraph.runs, context,
@@ -969,11 +971,25 @@ module Idml
969
971
  # frame: a StartParagraph forced break, or KeepAllLines-
970
972
  # Together with insufficient remaining space (never for the
971
973
  # frame's first paragraph, so progress is always made).
972
- def self.paragraph_deferred?(paragraph, layout_frame, font,
973
- cursor_y, bottom_limit, placed_any)
974
+ def self.paragraph_deferred?(paragraph, next_paragraph,
975
+ layout_frame, font, cursor_y,
976
+ bottom_limit, placed_any)
974
977
  return true if paragraph_break?(paragraph) && placed_any
975
- return false unless paragraph.keep_all_lines_together
976
978
  return false unless placed_any
979
+ return true if keep_with_next_break?(paragraph, next_paragraph,
980
+ layout_frame, font,
981
+ cursor_y, bottom_limit)
982
+
983
+ keep_all_lines_break?(paragraph, layout_frame, font,
984
+ cursor_y, bottom_limit)
985
+ end
986
+ private_class_method :paragraph_deferred?
987
+
988
+ # KeepAllLinesTogether: defer when the paragraph cannot
989
+ # fully fit the remaining space.
990
+ def self.keep_all_lines_break?(paragraph, layout_frame, font,
991
+ cursor_y, bottom_limit)
992
+ return false unless paragraph.keep_all_lines_together
977
993
 
978
994
  wrap_width = TextEngine::VerticalLayout.wrap_width(
979
995
  layout_frame, paragraph.right_indent || 0
@@ -983,7 +999,40 @@ module Idml
983
999
  )
984
1000
  (cursor_y - height) < bottom_limit
985
1001
  end
986
- private_class_method :paragraph_deferred?
1002
+ private_class_method :keep_all_lines_break?
1003
+
1004
+ # KeepWithNext: this paragraph defers when the next
1005
+ # paragraph is forced to the next frame, or when the next
1006
+ # paragraph's first line would not fit after this one.
1007
+ def self.keep_with_next_break?(paragraph, next_paragraph,
1008
+ layout_frame, font, cursor_y,
1009
+ bottom_limit)
1010
+ return false unless keepable_pair?(paragraph, next_paragraph)
1011
+ return true if paragraph_break?(next_paragraph)
1012
+
1013
+ wrap_width = TextEngine::VerticalLayout.wrap_width(
1014
+ layout_frame, paragraph.right_indent || 0
1015
+ )
1016
+ height = TextEngine::Measurement.paragraph_height(
1017
+ paragraph, font, wrap_width
1018
+ )
1019
+ (cursor_y - height - follower_leading(next_paragraph)) <
1020
+ bottom_limit
1021
+ end
1022
+ private_class_method :keep_with_next_break?
1023
+
1024
+ def self.keepable_pair?(paragraph, next_paragraph)
1025
+ !paragraph.keep_with_next.nil? && !next_paragraph.nil?
1026
+ end
1027
+ private_class_method :keepable_pair?
1028
+
1029
+ def self.follower_leading(next_paragraph)
1030
+ size = next_paragraph.runs.first&.point_size || DEFAULT_SIZE
1031
+ TextEngine::VerticalLayout.leading_for(
1032
+ next_paragraph.auto_leading, size
1033
+ )
1034
+ end
1035
+ private_class_method :follower_leading
987
1036
 
988
1037
  # True when the paragraph requests a forced break to the
989
1038
  # next frame/column (StartParagraph). All break flavors act
@@ -72,8 +72,11 @@ module Idml
72
72
  # NextColumn / NextFrame / NextOddPage / NextEvenPage).
73
73
  :start_paragraph,
74
74
  # Widow/orphan control: KeepAllLinesTogether pushes the whole
75
- # paragraph to the next frame when it cannot fully fit.
75
+ # paragraph to the next frame when it cannot fully fit;
76
+ # KeepWithNext binds this paragraph to the next paragraph's
77
+ # first line.
76
78
  :keep_all_lines_together,
79
+ :keep_with_next,
77
80
  :bullets_and_numbering_list_type,
78
81
  :bullet_character_value,
79
82
  :bullets_text_after,
@@ -207,6 +210,8 @@ module Idml
207
210
  keep_all_lines_together: resolve_attr(
208
211
  style_lookup, psr, :keep_all_lines_together
209
212
  ),
213
+ keep_with_next: resolve_attr(style_lookup, psr,
214
+ :keep_with_next),
210
215
  drop_cap_lines: resolve_attr(style_lookup, psr, :drop_cap_lines),
211
216
  drop_cap_characters: resolve_attr(style_lookup, psr,
212
217
  :drop_cap_characters),
data/lib/idml/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Idml
4
- VERSION = "0.9.8"
4
+ VERSION = "0.9.9"
5
5
  end
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.9.8
4
+ version: 0.9.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
@@ -185,6 +185,7 @@ 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
188
189
  - TODO.pdf/13-cjk-text-layout.md
189
190
  - TODO.pdf/14-page-item-element-models.md
190
191
  - TODO.pdf/15-wire-spread-children.md