idml 0.9.5 → 0.9.7
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/122-vertical-latin-ruby.md +29 -0
- data/TODO.pdf/123-keep-options.md +24 -0
- data/TODO.pdf/124-tatechuyoko-vertical.md +23 -0
- data/lib/idml/render/renderers/text_frame_renderer.rb +162 -24
- data/lib/idml/render/story_chain_controller.rb +3 -0
- data/lib/idml/render/style_resolver.rb +8 -0
- data/lib/idml/text_engine/vertical_text_layout.rb +33 -6
- data/lib/idml/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0d49d9954838f3326eab4e65e2915eab9cd18a526ce7c4dd58669e6dacb8910e
|
|
4
|
+
data.tar.gz: 56713c3d26980c24c6baf8d080543cd2471aef5dea4ca83c33be82f73fe2e83e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d52298b420e04ae5c4b8292e01d951eb23d03d4e01357f463111477e842aea9dd89f6e162a88ad05dd50fa8b721faf6522f891eb66e3405dd6207769f7a9bfe8
|
|
7
|
+
data.tar.gz: 1d7f89c274a8aac75868064826567901fbc0503ead631953fc7b24eaa282e0f1d8870d2e33f1dc6e81c834b4e3c00fc4b2b465b1a12cc349484b0e653628e63f
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# TODO PDF 122: Vertical mode completion — Latin rotation, vertical ruby
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE — implemented 2026-08-20
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
The vertical writing path (TODO 13, 2026-08-19) stacked every glyph
|
|
8
|
+
upright: Latin letters and digits rendered sideways-wrong (rotated
|
|
9
|
+
in appearance relative to convention), and ruby annotations were
|
|
10
|
+
not emitted at all in vertical mode.
|
|
11
|
+
|
|
12
|
+
## What was done
|
|
13
|
+
|
|
14
|
+
- Latin rotation: non-CJK glyphs in vertical text render rotated
|
|
15
|
+
90° clockwise (the vertical-writing convention for Latin runs),
|
|
16
|
+
via a graphics-state transform (q → translate → rotate −90° →
|
|
17
|
+
text at origin → Q). CJK glyphs stay upright.
|
|
18
|
+
- Vertical ruby: runs carrying RubyString emit the annotation
|
|
19
|
+
stacked vertically alongside the base glyphs — to the right of
|
|
20
|
+
the column by default, to the left for RubyPosition values
|
|
21
|
+
containing "Below" (mirroring the horizontal above/below
|
|
22
|
+
convention under rotation). RubyFontSize defaults to half the
|
|
23
|
+
base size.
|
|
24
|
+
|
|
25
|
+
## Known limitations
|
|
26
|
+
|
|
27
|
+
- Tate-chu-yoko (horizontal digit groups inside vertical text) and
|
|
28
|
+
mojikumi spacing classes remain documented stretch goals
|
|
29
|
+
(TODO 13).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# TODO PDF 123: Keep options (widow/orphan control)
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE — implemented 2026-08-20
|
|
4
|
+
|
|
5
|
+
## What was done
|
|
6
|
+
|
|
7
|
+
- `StyleResolver::Paragraph` carries `keep_all_lines_together`
|
|
8
|
+
from the PSR.
|
|
9
|
+
- `TextFrameRenderer.consume_paragraphs` pre-measures the paragraph
|
|
10
|
+
(TextEngine::Measurement) and, when KeepAllLinesTogether is set,
|
|
11
|
+
the paragraph cannot fully fit the remaining frame space, and at
|
|
12
|
+
least one paragraph was already placed, pushes the whole
|
|
13
|
+
paragraph to the next frame via the chain state — no widowed
|
|
14
|
+
first lines. The first paragraph in a frame never pushes, so
|
|
15
|
+
progress is always made.
|
|
16
|
+
|
|
17
|
+
## Known limitations
|
|
18
|
+
|
|
19
|
+
- KeepLinesTogether with per-side KeepFirstLines / KeepLastLines
|
|
20
|
+
counts (partial keep windows) is not modeled — only the
|
|
21
|
+
whole-paragraph KeepAllLinesTogether form.
|
|
22
|
+
- KeepWithNext (binding a paragraph to the next) is not modeled.
|
|
23
|
+
- Vertical writing mode does not apply keep options (column model
|
|
24
|
+
differs).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# TODO PDF 124: Tate-chu-yoko and vertical multi-run columns
|
|
2
|
+
|
|
3
|
+
## Status: COMPLETE — implemented 2026-08-20
|
|
4
|
+
|
|
5
|
+
## What was done
|
|
6
|
+
|
|
7
|
+
- Multi-run column continuity (bug fix): every run in a vertical
|
|
8
|
+
frame used to restart at column 0, overlapping earlier runs.
|
|
9
|
+
`VerticalTextLayout.layout` takes `start_column`; the frame
|
|
10
|
+
renderer threads the next-unused column across runs and
|
|
11
|
+
paragraphs, and persists it in the chain state (`column_offset`)
|
|
12
|
+
so vertical text continues correctly into the next frame of a
|
|
13
|
+
chain.
|
|
14
|
+
- Tate-chu-yoko (`Tatechuyoko="true"` on the CSR — note the
|
|
15
|
+
schema's lowercase c): the run's glyphs render HORIZONTALLY,
|
|
16
|
+
sharing one baseline at the top of their column slot and centered
|
|
17
|
+
in it, consuming one column. Groups wider than the column height
|
|
18
|
+
fall back to stacked layout. StyledRun carries `tatechuyoko`.
|
|
19
|
+
|
|
20
|
+
## Remaining from TODO 13
|
|
21
|
+
|
|
22
|
+
Mojikumi (punctuation spacing classes) is the last documented
|
|
23
|
+
stretch goal.
|
|
@@ -177,17 +177,20 @@ module Idml
|
|
|
177
177
|
layout_frame = layout_frame(context.item, box)
|
|
178
178
|
left_limit = layout_frame.x + (layout_frame.inset_left || 0)
|
|
179
179
|
resume_vertical_paragraph(state)
|
|
180
|
+
next_column = state.column_offset || 0
|
|
180
181
|
pending = 0
|
|
181
182
|
|
|
182
183
|
state.paragraphs.each do |paragraph|
|
|
183
|
-
remaining_runs = vertical_paragraph(
|
|
184
|
-
canvas, paragraph, context, layout_frame, font,
|
|
184
|
+
remaining_runs, next_column = vertical_paragraph(
|
|
185
|
+
canvas, paragraph, context, layout_frame, font,
|
|
186
|
+
left_limit, next_column
|
|
185
187
|
)
|
|
186
188
|
if remaining_runs.empty?
|
|
187
189
|
pending += 1
|
|
188
190
|
else
|
|
189
191
|
state.current_paragraph = paragraph
|
|
190
192
|
state.runs_remaining = remaining_runs
|
|
193
|
+
state.column_offset = next_column
|
|
191
194
|
break
|
|
192
195
|
end
|
|
193
196
|
end
|
|
@@ -198,7 +201,9 @@ module Idml
|
|
|
198
201
|
|
|
199
202
|
# Folds a chain-resumed paragraph (runs_remaining) back to
|
|
200
203
|
# the head of the paragraph list so the vertical path sees a
|
|
201
|
-
# uniform sequence.
|
|
204
|
+
# uniform sequence. The consumed column offset persists in
|
|
205
|
+
# the state so the next frame continues right where this
|
|
206
|
+
# frame's columns ended.
|
|
202
207
|
def self.resume_vertical_paragraph(state)
|
|
203
208
|
return unless state.current_paragraph
|
|
204
209
|
|
|
@@ -209,10 +214,13 @@ module Idml
|
|
|
209
214
|
end
|
|
210
215
|
private_class_method :resume_vertical_paragraph
|
|
211
216
|
|
|
212
|
-
# Renders one paragraph's runs as vertical columns
|
|
213
|
-
#
|
|
217
|
+
# Renders one paragraph's runs as vertical columns starting
|
|
218
|
+
# at `start_column`. Returns [runs that did not fit (empty
|
|
219
|
+
# when all placed), the next unused column index].
|
|
214
220
|
def self.vertical_paragraph(canvas, paragraph, context,
|
|
215
|
-
layout_frame, font, left_limit
|
|
221
|
+
layout_frame, font, left_limit,
|
|
222
|
+
start_column)
|
|
223
|
+
next_column = start_column
|
|
216
224
|
paragraph.runs.each_with_index do |run, index|
|
|
217
225
|
size = run.point_size || DEFAULT_SIZE
|
|
218
226
|
leading = TextEngine::VerticalLayout.leading_for(
|
|
@@ -220,22 +228,71 @@ module Idml
|
|
|
220
228
|
)
|
|
221
229
|
glyphs = TextEngine::Shaper.shape(text: run.text, font: font,
|
|
222
230
|
size: size)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
231
|
+
grouped = tatechuyoko_group(run, glyphs, layout_frame,
|
|
232
|
+
leading, size, next_column)
|
|
233
|
+
positioned, next_column = grouped ||
|
|
234
|
+
TextEngine::VerticalTextLayout.layout(
|
|
235
|
+
glyphs: glyphs, frame: layout_frame, leading: leading,
|
|
236
|
+
size: size, start_column: next_column
|
|
237
|
+
)
|
|
238
|
+
unless vertical_fits?(layout_frame, leading, next_column,
|
|
228
239
|
left_limit)
|
|
229
|
-
return paragraph.runs[index..]
|
|
240
|
+
return [paragraph.runs[index..], start_column]
|
|
230
241
|
end
|
|
231
242
|
|
|
243
|
+
emit_vertical_run(canvas, positioned, grouped, run, context,
|
|
244
|
+
size)
|
|
245
|
+
emit_vertical_ruby(canvas, run, positioned, layout_frame,
|
|
246
|
+
size, context)
|
|
247
|
+
end
|
|
248
|
+
[[], next_column]
|
|
249
|
+
end
|
|
250
|
+
private_class_method :vertical_paragraph
|
|
251
|
+
|
|
252
|
+
# A tate-chu-yoko group for runs that declare it (and whose
|
|
253
|
+
# glyphs fit horizontally in a column slot); nil otherwise.
|
|
254
|
+
def self.tatechuyoko_group(run, glyphs, layout_frame, leading,
|
|
255
|
+
size, start_column)
|
|
256
|
+
return nil unless run.tatechuyoko
|
|
257
|
+
|
|
258
|
+
TextEngine::VerticalTextLayout.tatechuyoko_group(
|
|
259
|
+
glyphs: glyphs, frame: layout_frame, leading: leading,
|
|
260
|
+
size: size, start_column: start_column
|
|
261
|
+
)
|
|
262
|
+
end
|
|
263
|
+
private_class_method :tatechuyoko_group
|
|
264
|
+
|
|
265
|
+
# Emits a vertical run's glyphs: rotated/normal vertical
|
|
266
|
+
# handling for stacked columns, plain upright emission for
|
|
267
|
+
# tate-chu-yoko groups.
|
|
268
|
+
def self.emit_vertical_run(canvas, positioned, grouped, run,
|
|
269
|
+
context, size)
|
|
270
|
+
if grouped
|
|
271
|
+
positioned.each do |glyph|
|
|
272
|
+
emit_upright_glyph(canvas, glyph, run, context, size)
|
|
273
|
+
end
|
|
274
|
+
else
|
|
232
275
|
positioned.each do |glyph|
|
|
233
276
|
emit_vertical_glyph(canvas, glyph, run, context, size)
|
|
234
277
|
end
|
|
235
278
|
end
|
|
236
|
-
[]
|
|
237
279
|
end
|
|
238
|
-
private_class_method :
|
|
280
|
+
private_class_method :emit_vertical_run
|
|
281
|
+
|
|
282
|
+
def self.emit_upright_glyph(canvas, positioned, run, context,
|
|
283
|
+
size)
|
|
284
|
+
text = [positioned.codepoint].pack("U")
|
|
285
|
+
text_kwargs = CharacterStyle.text_kwargs(
|
|
286
|
+
run,
|
|
287
|
+
{
|
|
288
|
+
at: [positioned.x, positioned.y],
|
|
289
|
+
font: font_for_run(run, context),
|
|
290
|
+
size: size,
|
|
291
|
+
},
|
|
292
|
+
)
|
|
293
|
+
canvas.text(text, **text_kwargs)
|
|
294
|
+
end
|
|
295
|
+
private_class_method :emit_upright_glyph
|
|
239
296
|
|
|
240
297
|
def self.vertical_fits?(layout_frame, leading, columns, left_limit)
|
|
241
298
|
right = layout_frame.x + layout_frame.width -
|
|
@@ -246,21 +303,80 @@ module Idml
|
|
|
246
303
|
|
|
247
304
|
# Emits one upright glyph at its vertical position with the
|
|
248
305
|
# run's character styling.
|
|
306
|
+
# Emits one glyph in vertical mode: CJK upright; Latin
|
|
307
|
+
# (non-CJK) rotated 90° clockwise per the vertical-writing
|
|
308
|
+
# convention, via a graphics-state transform.
|
|
249
309
|
def self.emit_vertical_glyph(canvas, positioned, run, context,
|
|
250
310
|
size)
|
|
251
311
|
text = [positioned.codepoint].pack("U")
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
312
|
+
if TextEngine::CjkLayout.cjk?(positioned.codepoint)
|
|
313
|
+
text_kwargs = CharacterStyle.text_kwargs(
|
|
314
|
+
run,
|
|
315
|
+
{
|
|
316
|
+
at: [positioned.x, positioned.y],
|
|
317
|
+
font: font_for_run(run, context),
|
|
318
|
+
size: size,
|
|
319
|
+
},
|
|
320
|
+
)
|
|
321
|
+
canvas.text(text, **text_kwargs)
|
|
322
|
+
else
|
|
323
|
+
canvas.save_graphics_state do
|
|
324
|
+
# 90° clockwise: exact matrix (rotate would emit
|
|
325
|
+
# cos/sin float noise like 6.1e-17).
|
|
326
|
+
canvas.concat(0, -1, 1, 0, positioned.x, positioned.y)
|
|
327
|
+
text_kwargs = CharacterStyle.text_kwargs(
|
|
328
|
+
run,
|
|
329
|
+
{
|
|
330
|
+
at: [0, 0],
|
|
331
|
+
font: font_for_run(run, context),
|
|
332
|
+
size: size,
|
|
333
|
+
},
|
|
334
|
+
)
|
|
335
|
+
canvas.text(text, **text_kwargs)
|
|
336
|
+
end
|
|
337
|
+
end
|
|
261
338
|
end
|
|
262
339
|
private_class_method :emit_vertical_glyph
|
|
263
340
|
|
|
341
|
+
# Emits the run's ruby alongside its vertical glyphs:
|
|
342
|
+
# stacked top-to-bottom beside the column — right side by
|
|
343
|
+
# default, left for Below* RubyPosition values (the
|
|
344
|
+
# horizontal above/below convention mirrored under
|
|
345
|
+
# rotation).
|
|
346
|
+
def self.emit_vertical_ruby(canvas, run, positioned, frame,
|
|
347
|
+
base_size, context)
|
|
348
|
+
ruby_text = run.ruby_string
|
|
349
|
+
return if ruby_text.nil? || ruby_text.empty?
|
|
350
|
+
|
|
351
|
+
ruby_size = run.ruby_font_size ||
|
|
352
|
+
(base_size * RUBY_SIZE_FACTOR)
|
|
353
|
+
x = ruby_vertical_x(positioned.first, run, base_size)
|
|
354
|
+
y = frame.y + frame.height - (frame.inset_top || 0)
|
|
355
|
+
ruby_text.each_codepoint do |codepoint|
|
|
356
|
+
text_kwargs = CharacterStyle.text_kwargs(
|
|
357
|
+
run,
|
|
358
|
+
{
|
|
359
|
+
at: [x, y - ruby_size],
|
|
360
|
+
font: font_for_run(run, context),
|
|
361
|
+
size: ruby_size,
|
|
362
|
+
},
|
|
363
|
+
)
|
|
364
|
+
canvas.text([codepoint].pack("U"), **text_kwargs)
|
|
365
|
+
y -= ruby_size
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
private_class_method :emit_vertical_ruby
|
|
369
|
+
|
|
370
|
+
def self.ruby_vertical_x(positioned, run, base_size)
|
|
371
|
+
offset = base_size * 0.9
|
|
372
|
+
if run.ruby_position.to_s.include?("Below")
|
|
373
|
+
positioned.x - offset
|
|
374
|
+
else
|
|
375
|
+
positioned.x + offset
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
private_class_method :ruby_vertical_x
|
|
379
|
+
|
|
264
380
|
def self.chain_head?(frame)
|
|
265
381
|
frame.previous_text_frame.nil? || frame.previous_text_frame == "n"
|
|
266
382
|
end
|
|
@@ -515,7 +631,9 @@ module Idml
|
|
|
515
631
|
pending = 0
|
|
516
632
|
state.paragraphs.each do |paragraph|
|
|
517
633
|
break if cursor_y < bottom_limit
|
|
518
|
-
break if
|
|
634
|
+
break if paragraph_deferred?(paragraph, layout_frame, font,
|
|
635
|
+
cursor_y, bottom_limit,
|
|
636
|
+
pending.positive?)
|
|
519
637
|
|
|
520
638
|
remaining_runs, cursor_y, char_cursor = render_runs_for_paragraph(
|
|
521
639
|
canvas, paragraph, paragraph.runs, context,
|
|
@@ -847,6 +965,26 @@ module Idml
|
|
|
847
965
|
end
|
|
848
966
|
private_class_method :layout_run
|
|
849
967
|
|
|
968
|
+
# True when the paragraph should move wholly to the next
|
|
969
|
+
# frame: a StartParagraph forced break, or KeepAllLines-
|
|
970
|
+
# Together with insufficient remaining space (never for the
|
|
971
|
+
# 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
|
+
return true if paragraph_break?(paragraph) && placed_any
|
|
975
|
+
return false unless paragraph.keep_all_lines_together
|
|
976
|
+
return false unless placed_any
|
|
977
|
+
|
|
978
|
+
wrap_width = TextEngine::VerticalLayout.wrap_width(
|
|
979
|
+
layout_frame, paragraph.right_indent || 0
|
|
980
|
+
)
|
|
981
|
+
height = TextEngine::Measurement.paragraph_height(
|
|
982
|
+
paragraph, font, wrap_width
|
|
983
|
+
)
|
|
984
|
+
(cursor_y - height) < bottom_limit
|
|
985
|
+
end
|
|
986
|
+
private_class_method :paragraph_deferred?
|
|
987
|
+
|
|
850
988
|
# True when the paragraph requests a forced break to the
|
|
851
989
|
# next frame/column (StartParagraph). All break flavors act
|
|
852
990
|
# at frame/column granularity.
|
|
@@ -48,6 +48,7 @@ module Idml
|
|
|
48
48
|
:ruby_string,
|
|
49
49
|
:ruby_font_size,
|
|
50
50
|
:ruby_position,
|
|
51
|
+
:tatechuyoko,
|
|
51
52
|
keyword_init: true,
|
|
52
53
|
)
|
|
53
54
|
|
|
@@ -70,6 +71,9 @@ module Idml
|
|
|
70
71
|
# Forced paragraph break (StartParagraph: NextPage /
|
|
71
72
|
# NextColumn / NextFrame / NextOddPage / NextEvenPage).
|
|
72
73
|
:start_paragraph,
|
|
74
|
+
# Widow/orphan control: KeepAllLinesTogether pushes the whole
|
|
75
|
+
# paragraph to the next frame when it cannot fully fit.
|
|
76
|
+
:keep_all_lines_together,
|
|
73
77
|
:bullets_and_numbering_list_type,
|
|
74
78
|
:bullet_character_value,
|
|
75
79
|
:bullets_text_after,
|
|
@@ -200,6 +204,9 @@ module Idml
|
|
|
200
204
|
),
|
|
201
205
|
start_paragraph: resolve_attr(style_lookup, psr,
|
|
202
206
|
:start_paragraph),
|
|
207
|
+
keep_all_lines_together: resolve_attr(
|
|
208
|
+
style_lookup, psr, :keep_all_lines_together
|
|
209
|
+
),
|
|
203
210
|
drop_cap_lines: resolve_attr(style_lookup, psr, :drop_cap_lines),
|
|
204
211
|
drop_cap_characters: resolve_attr(style_lookup, psr,
|
|
205
212
|
:drop_cap_characters),
|
|
@@ -390,6 +397,7 @@ module Idml
|
|
|
390
397
|
ruby_string: resolve_csr(style_lookup, csr, :ruby_string),
|
|
391
398
|
ruby_font_size: resolve_csr(style_lookup, csr, :ruby_font_size),
|
|
392
399
|
ruby_position: resolve_csr(style_lookup, csr, :ruby_position),
|
|
400
|
+
tatechuyoko: resolve_csr(style_lookup, csr, :tatechuyoko),
|
|
393
401
|
)
|
|
394
402
|
end
|
|
395
403
|
private_class_method :text_run
|
|
@@ -13,18 +13,19 @@ module Idml
|
|
|
13
13
|
PositionedGlyph = Struct.new(:codepoint, :x, :y, keyword_init: true)
|
|
14
14
|
|
|
15
15
|
# Stacks `glyphs` into columns of at most the frame's text
|
|
16
|
-
# height; column 0 is the rightmost.
|
|
17
|
-
#
|
|
18
|
-
|
|
16
|
+
# height; column 0 is the rightmost. `start_column` places
|
|
17
|
+
# the first column after already-used columns (multi-run
|
|
18
|
+
# frames). Returns [positioned_glyphs, next_column_index].
|
|
19
|
+
def self.layout(glyphs:, frame:, leading:, size:, start_column: 0)
|
|
19
20
|
lines = TextEngine::LineBreaker.break(
|
|
20
21
|
glyphs: glyphs, frame_width: column_height(frame),
|
|
21
22
|
)
|
|
22
23
|
positioned = []
|
|
23
|
-
lines.each_with_index do |line,
|
|
24
|
+
lines.each_with_index do |line, index|
|
|
24
25
|
stack_column(positioned, line.glyphs, frame, leading, size,
|
|
25
|
-
|
|
26
|
+
index + start_column)
|
|
26
27
|
end
|
|
27
|
-
[positioned, lines.length]
|
|
28
|
+
[positioned, start_column + lines.length]
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
# The frame's usable vertical extent for one column of text.
|
|
@@ -47,6 +48,32 @@ module Idml
|
|
|
47
48
|
end
|
|
48
49
|
private_class_method :stack_column
|
|
49
50
|
|
|
51
|
+
# Positions a tate-chu-yoko group (e.g. the digits "12" kept
|
|
52
|
+
# horizontal inside vertical text): glyphs share one baseline
|
|
53
|
+
# at the top of the given column slot and advance left-to-
|
|
54
|
+
# right, centered in the slot. Returns
|
|
55
|
+
# [positioned_glyphs, next_column_index]; nil when the group
|
|
56
|
+
# is wider than the column height (caller falls back to
|
|
57
|
+
# stacked layout).
|
|
58
|
+
def self.tatechuyoko_group(glyphs:, frame:, leading:, size:,
|
|
59
|
+
start_column:)
|
|
60
|
+
total = glyphs.sum(&:width)
|
|
61
|
+
return nil if total > column_height(frame)
|
|
62
|
+
|
|
63
|
+
right = frame.x + frame.width - (frame.inset_right || 0)
|
|
64
|
+
slot_center = right - ((start_column + 0.5) * leading)
|
|
65
|
+
x = slot_center - (total / 2.0)
|
|
66
|
+
baseline_y = frame.y + frame.height - (frame.inset_top || 0) - size
|
|
67
|
+
positioned = glyphs.map do |glyph|
|
|
68
|
+
glyph_position = PositionedGlyph.new(
|
|
69
|
+
codepoint: glyph.codepoint, x: x, y: baseline_y,
|
|
70
|
+
)
|
|
71
|
+
x += glyph.width
|
|
72
|
+
glyph_position
|
|
73
|
+
end
|
|
74
|
+
[positioned, start_column + 1]
|
|
75
|
+
end
|
|
76
|
+
|
|
50
77
|
# Vertical advance of one glyph — its measured width (the em
|
|
51
78
|
# for the common square-CJK case).
|
|
52
79
|
def self.advance(glyph)
|
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.9.
|
|
4
|
+
version: 0.9.7
|
|
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-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bigdecimal
|
|
@@ -180,6 +180,9 @@ files:
|
|
|
180
180
|
- TODO.pdf/12-idml-to-pdf-pipeline.md
|
|
181
181
|
- TODO.pdf/120-start-paragraph-breaks.md
|
|
182
182
|
- TODO.pdf/121-cell-edge-strokes.md
|
|
183
|
+
- TODO.pdf/122-vertical-latin-ruby.md
|
|
184
|
+
- TODO.pdf/123-keep-options.md
|
|
185
|
+
- TODO.pdf/124-tatechuyoko-vertical.md
|
|
183
186
|
- TODO.pdf/13-cjk-text-layout.md
|
|
184
187
|
- TODO.pdf/14-page-item-element-models.md
|
|
185
188
|
- TODO.pdf/15-wire-spread-children.md
|