pub fn resolve(&self, font_size_px: f32, ascent: f32, descent: f32, line_gap: f32, units_per_em: u16) -> f32 {
pub fn resolve_with_metrics(&self, font_size_px: f32, metrics: &LayoutFontMetrics) -> f32 {
self.resolve(font_size_px, metrics.ascent, metrics.descent, metrics.line_gap, metrics.units_per_em)
FontStack::Stack(selectors) => FontChainKeyOrRef::Chain(FontChainKey::from_selectors(selectors)),
pub fn get_embedded_font_by_hash(&self, font_hash: u64) -> Option<azul_css::props::basic::FontRef> {
/// * Per spec: "logical width of a line box is equal to the inner logical width of its containing
/// +spec:box-model:51342f - inline box margins/borders/padding do not affect line box height (default leading mode)
/// +spec:font-metrics:618776 - line-fit-edge (cap, ex, ideographic, alphabetic edge selection) not yet implemented
/// +spec:box-model:c09331 - text-box-trim trims block container first/last line to font metrics
/// // +spec:overflow:dc2196 - text-box-trim overflow handled as normal overflow (no special handling needed)
// +spec:box-model:415ef3 - initial letters use standard margin/padding/border box model; exclusion area = margin box
// +spec:box-model:d53ea3 - when block-start padding+border are zero, content edge coincides with over alignment point
// +spec:writing-modes:6c5ab9 - blocks inherit base direction from parent via CSS direction property
// +spec:display-property:3bcac8 - inline boxes sized in block axis based on font metrics (ascent/descent)
// +spec:font-metrics:df51b1 - font metrics (ascent, descent, line_gap) used as baselines for inline layout alignment and box sizing
// +spec:box-model:a2f1c1 - inline box content area sized from first available font metrics (ascent/descent)
// +spec:font-metrics:797593 - font metrics (ascent, descent, line-gap) used for baseline calculations
// +spec:font-metrics:eb97e0 - Font baseline metrics (ascent/descent) from font tables used for baseline alignment
// +spec:font-metrics:f2cd75 - em-over/em-under baselines intentionally not included (not used by CSS per spec)
// +spec:inline-formatting-context:76cd57 - ascent/descent font metrics for inline formatting context layout
// +spec:font-metrics:006bd8 - baseline position from font design coordinates, scaled with font size
// +spec:font-metrics:910c0a - dominant-baseline: auto resolves to alphabetic for horizontal text
// +spec:line-height:471816 - line gap metric extracted from font for optional use when line-height is normal
// +spec:writing-modes:451a3e - ascent/descent/line-gap metrics: prefer OS/2, fallback HHEA, floor line_gap at 0
// +spec:font-metrics:332c16 - text-over/text-under baseline metrics derived from font ascent/descent
// +spec:font-metrics:9895e2 - baseline table is a font-level property; metrics apply uniformly to all glyphs
// +spec:table-layout:6bbd10 - use sTypoAscender/sTypoDescender as ascent/descent metrics per spec recommendation
// +spec:font-metrics:a55c05 - metrics taken from font, synthesized if missing (prefers OS/2, falls back to HHEA)
// +spec:line-breaking:841a87 - hyphens property: manual (U+00AD/U+2010 only) and auto (language-aware automatic hyphenation)
// +spec:line-breaking:68c6ad - hyphens property controls hyphenation opportunities (none/manual/auto)
// +spec:line-breaking:ce5258 - white-space property controls collapsing, wrapping, and forced breaks
// +spec:white-space-processing:dec7aa - White space not removed/collapsed is "preserved white space"
// +spec:display-property:162c99 - Initial letter box: in-flow inline-level box with special layout behavior
// +spec:display-property:72a797 - Initial letter handled like inline-level content in originating line box
// +spec:containing-block:46a499 - subsequent block must clear previous block's initial letter if it starts with its own initial letter, establishes independent FC, or specifies clear in initial letter's CB start direction
// +spec:font-metrics:1e5325 - drop initial cap-height = (N-1)*line_height + surrounding cap-height
// +spec:font-metrics:3aa518 - initial-letter-align: cap-height/ideographic/hanging/leading/border-box baseline alignment
// +spec:writing-modes:9698b0 - Han-derived scripts: initial letter extends from block-start to block-end of Nth line
height: self.style.line_height.resolve_with_metrics(self.style.font_size_px, &self.font_metrics),
// +spec:font-metrics:fa104e - vertical-align values; baseline-source defaults to auto (first baseline)
// +spec:inline-formatting-context:340729 - alignment-baseline values for IFC baseline alignment (only baseline/top/bottom/middle implemented)
// +spec:display-property:0b1deb - inline boxes use dominant baseline to align text and inline-level children
// +spec:inline-formatting-context:3996a6 - dominant-baseline defaults to alphabetic in horizontal mode; vertical-align handles baseline alignment and super/sub shifting
// +spec:font-metrics:152df3 - Raise (positive) or lower (negative) by this distance; 0 = baseline
// +spec:box-model:da0ba2 - RTL bidi inline box split: left/right edges assigned to correct fragments
// +spec:box-model:e9144f - visual-order margin/border/padding for inline boxes in bidi context
// +spec:box-model:fac66f - Assigns margins/borders/padding in visual order for bidi inline fragments
// +spec:box-model:720688 - LTR: left on first, right on last; RTL: right on first, left on last
// +spec:positioning:1fcad6 - bidi-aware margin/border/padding on inline box fragments per visual order
// +spec:box-model:bae97f - visual-order margin/border/padding assignment for bidi inline fragments
// +spec:text-alignment-spacing:25e82a - text-align shorthand resolves text-align-all / text-align-last
/// When text-align-last is auto (default), justify falls back to start; others use text-align.
// +spec:text-alignment-spacing:bca77d - text-align-last auto falls back to text-align-all, justify→start
// +spec:line-breaking:9b10d2 - text-align-last applies to last line and lines before forced breaks
/// +spec:text-alignment-spacing:8d88ce - text-align-last overrides justify on last line/forced break
// +spec:line-breaking:d70ffd - Defines forced line break (Hard) vs soft wrap break (Soft) types
VerticalRl, // +spec:writing-modes:6e22a7 - vertical-rl (vertical right-to-left, commonly used in East Asia)
// +spec:block-formatting-context:458d31 - vertical text orientation: upright for horizontal scripts, intrinsic for vertical scripts
// +spec:display-property:b1533f - text-combine-upright tate-chu-yoko horizontal-in-vertical composition
// +spec:containing-block:e7a271 - paragraph embedding level set from containing block's 'direction' property
// +spec:display-property:7665cb - inline boxes split into multiple visual runs due to bidi text processing
// +spec:display-property:929d6b - applies Unicode bidi algorithm to inline-level box sequences
// +spec:display-property:e8584a - Apply Unicode bidi algorithm to inline-level box sequences per CSS Writing Modes §2.4
// +spec:containing-block:961e3c - bidi paragraph level from containing block direction, not UAX9 heuristic
// +spec:writing-modes:0a5368 - unicode-bidi: plaintext auto-detects direction from text content
reorder_logical_items(&logical_items, base_direction, unicode_bidi_val, debug_messages).unwrap(),
let mut cursor = BreakCursor::with_word_break(&oriented_items, first_constraints.word_break);
reorder_logical_items(&logical_items, base_direction, unicode_bidi_val, debug_messages).unwrap(),
let mut run_overrides: HashMap<u32, HashMap<u32, &PartialStyleProperties>> = HashMap::new();
// +spec:containing-block:e4d9de - text-combine-upright digit run rules: digits sharing an ancestor with same value form one sequence across box boundaries
// +spec:inline-formatting-context:f65029 - text-combine-upright text run rules: combine consecutive digits not interrupted by box boundary
// +spec:containing-block:9a26bd - text-combine-upright digit runs scoped by ancestor style boundaries
// +spec:block-formatting-context:9e7c79 - text-combine-upright combines multiple characters into 1em in vertical writing
// +spec:containing-block:2b399b - text-combine-upright digits: combine ASCII digit sequences within max_digits limit; box boundaries implicitly prevent cross-box combination
// +spec:white-space-processing:409d90 - text-combine-upright combined text: white space at start/end processed as in inline-block
// +spec:inline-block:d47971 - unicode-bidi:plaintext uses P2/P3 heuristic for base direction (implemented via get_base_direction)
// +spec:writing-modes:287491 - BiDi reordering and base direction detection (Appendix A text processing order)
// +spec:containing-block:149255 - bidi reordering produces inline box fragments that may separate in wide containing blocks
// +spec:containing-block:c7c08f - bidi reordering produces inline box fragments that may be adjacent in narrow containing blocks
// +spec:containing-block:2936ae - bidi reordering splits inline boxes into visual fragments (CSS Writing Modes 4 §2.4.5)
// +spec:display-property:0cdbd3 - bidi reordering splits inline boxes into visual runs; each run is shaped/formatted independently
// +spec:display-property:0d62a2 - bidi reordering of inline content respects block direction and unicode-bidi embedding
// +spec:display-property:58b30a - bidi paragraph breaks within inline boxes: each IFC does independent bidi analysis, so splitting an inline box at a paragraph boundary naturally closes/reopens bidi embeddings
// +spec:writing-modes:330b8f - text ordered according to Unicode bidi algorithm after white-space processing
// +spec:writing-modes:7a9e7d - bidi control translation: text passed to unicode_bidi for reordering
// +spec:writing-modes:8e7281 - unicode-bidi property: bidi control codes inserted via BidiInfo
// +spec:writing-modes:809513 - bidi string built across inline element boundaries; unicode-bidi:normal adds no extra embedding levels
// +spec:containing-block:1fdc31 - inline boxes with unicode-bidi:normal are transparent to bidi algorithm
// +spec:display-property:8409d3 - inline-level elements with unicode-bidi:normal have no effect on bidi ordering; embed creates an embedding
// +spec:display-property:89464a - inline boxes with unicode-bidi:normal don't open embedding levels, so direction has no effect on bidi reordering
// +spec:display-property:d47971 - bidi control codes should be injected at inline box boundaries based on unicode-bidi + direction
// +spec:display-property:de657b - bidi control codes injected for display:inline boxes per unicode-bidi value
// +spec:display-property:f01a81 - bidi-override should prepend LRO/RLO and append PDF per unicode-bidi CSS property (not yet implemented)
// +spec:display-property:fcb011 - unicode-bidi values on inline boxes insert bidi control codes
// +spec:writing-modes:d490bf - direction only affects reordering when unicode-bidi is embed/override (not yet enforced for inline elements)
// +spec:display-property:1a6075 - paragraph embedding level set from direction property per UAX9 HL1
/// // +spec:display-property:9c6d59 - text shaping not broken across inline box boundaries when no effective formatting change
eprintln!("[FONT FALLBACK] no font could render any char in '{}'", text.chars().take(20).collect::<String>());
eprintln!("[FONT FALLBACK] font {:?} not in loaded_fonts for '{}'", font_id, text.chars().take(20).collect::<String>());
eprintln!("[FONT FALLBACK] font {:?} NOT loaded, skipping segment bytes {}..{}", font_id, seg_start, seg_end);
// +spec:display-property:ca95f6 - text shaping breaks at inline box boundaries when layout-affecting properties differ
let shaped_clusters_result: Result<Vec<ShapedCluster>, LayoutError> = match &style.font_stack {
let shaped_clusters_result: Result<Vec<ShapedCluster>, LayoutError> = match &style.font_stack {
// +spec:width-calculation:657f75 - convert full-width chars to non-full-width before compression
// +spec:width-calculation:d0a295 - full-width digit conversion example (e.g. "23" stays narrow)
// +spec:block-formatting-context:dc4549 - text-combine-upright compression: UA may scale composition to match 水 advance height
// +spec:box-model:8bbcd1 - non-zero inline-axis borders/padding between hangable glyph and line edge prevent hanging
// +spec:block-formatting-context:227171 - vertical glyph orientation with fallback vertical metrics
// +spec:block-formatting-context:df20a5 - mixed vertical orientation dispatch (TextOrientation::Mixed)
let fallback_advance = cluster.style.line_height.resolve_with_metrics(cluster.style.font_size_px, &glyph.font_metrics);
/// Approximate version of get_item_vertical_metrics for use without constraints (e.g. bounds()).
let resolved_lh = c.style.line_height.resolve_with_metrics(glyph.style.font_size_px, &glyph.font_metrics);
/// Gets the ascent (distance from baseline to top) and descent (distance from baseline to bottom)
// +spec:box-model:37aeb2 - inline box margins/borders/padding do not affect line box height (leading model)
// +spec:display-property:184f0d - Inline box baseline derives from first available font metrics
// +spec:display-property:238bf5 - Inline box layout bounds from own text metrics, not child boxes
// +spec:display-property:29b194 - baseline determination for inline boxes (CSS Box Alignment 3 §9.1)
// +spec:display-property:2987db - per-glyph font metrics impact inline box layout bounds (line-height: normal caveat not yet distinguished)
/// +spec:display-property:fd42a9 - line-height affects line box contribution, not inline box size
// +spec:font-metrics:506abb - A/D from font metrics with half-leading: L = line-height - (A+D), A' = A + L/2, D' = D + L/2
// +spec:font-metrics:773029 - ascent/descent font metrics used for baseline calculations (visual centering depends on these)
// +spec:font-metrics:f42870 - half-leading model: leading = line-height - (ascent + descent), distributed equally above/below
// +spec:writing-modes:531c2e - UAs should use vertical baseline tables in vertical typographic modes
pub fn get_item_vertical_metrics(item: &ShapedItem, constraints: &UnifiedConstraints) -> (f32, f32) {
// +spec:display-property:626c86 - strut for inline box with no glyphs uses first available font metrics
// +spec:box-model:0b3e1f - inline non-replaced box height uses only line-height, not vertical padding/border/margin
// +spec:display-property:d52f26 - layout bounds enclose all glyphs from highest A to deepest D
// +spec:font-metrics:790fd2 - half-leading: L = line-height - (A+D), A' = A + L/2, D' = D + L/2
// +spec:line-height:0078fa - half-leading: L = line-height - (A+D), distributed equally above/below
// +spec:height-calculation:eb98b5 - multi-font normal line-height uses max across glyph metrics
let resolved_lh = glyph.style.line_height.resolve_with_metrics(glyph.style.font_size_px, &glyph.font_metrics);
// +spec:block-formatting-context:861155 - vertical-align affects vertical positioning inside line box for inline-level elements
/// // +spec:display-contents:66d910 - line box height fitted to contents, controlled by line-height
// +spec:inline-formatting-context:c3fc54 - line box tall enough for all boxes, vertical-align determines alignment within line box
// +spec:box-model:c9bcd7 - when line-fit-edge is not leading, layout bounds inflated by margin+border+padding (not yet implemented; default leading behavior is correct)
// +spec:font-metrics:95152b - baseline alignment: items with different font sizes aligned by matching alphabetic baselines
// +spec:line-breaking:90c1bd - only auto-hyphenate when language is known and hyphenation resource available
let is_min_content = matches!(fragment_constraints.available_width, AvailableSpace::MinContent);
let is_max_content = matches!(fragment_constraints.available_width, AvailableSpace::MaxContent);
unicode_bidi::Direction::Mixed => fragment_constraints.direction.unwrap_or(BidiDirection::Ltr),
// +spec:overflow:b932c4 - overflow-wrap/word-wrap (normal/break-word/anywhere) and hyphens interaction
let effective_overflow_wrap = if is_min_content && fragment_constraints.overflow_wrap == OverflowWrap::Anywhere {
} else if is_min_content && fragment_constraints.overflow_wrap == OverflowWrap::BreakWord {
// +spec:display-property:2608cc - inline box splitting across line boxes, overflow for unsplittable boxes
break_one_line(cursor, &line_constraints, false, hyphenator.as_ref(), fonts, fragment_constraints.line_break, fragment_constraints.white_space_mode, effective_overflow_wrap);
let line_ends_with_forced_break = line_items.iter().any(|item| matches!(item, ShapedItem::Break { .. }));
// +spec:display-property:6c4978 - line-height on block container establishes minimum line box height
/// - If `line_constraints.total_available` is 0.0 (from `available_width: 0.0` bug), every word
/// // +spec:intrinsic-sizing:6085cf - hanging glyphs must be excluded from intrinsic size computation
/// - word-break property (normal, break-all, keep-all) - IMPLEMENTED via BreakCursor.word_break
/// - \u26a0\ufe0f line-break property: anywhere implemented; loose/normal/strict CJK strictness
// +spec:white-space-processing:c83dbd - Phase II: collapsible spaces at line start removed, trailing spaces removed, tab stops
// +spec:white-space-processing:fef250 - Phase II: trailing collapsible spaces and U+1680 removed at line end
// as well as any trailing U+1680 OGHAM SPACE MARK whose white-space is normal/nowrap/pre-line.
// +spec:line-breaking:d7ed93 - language-specific hyphenation rules apply to both auto and explicit (soft hyphen) opportunities
/// A tuple containing the `Vec` of positioned items and the calculated height of the line box.
/// +spec:containing-block:8d5146 - text-align aligns within line box, not viewport/containing block
/// - \u26a0\ufe0f If segment.width is infinite (from intrinsic sizing), sets alignment_offset=0 to
/// - The function assumes `line_index == 0` means first line for text-indent. A more robust system
/// // +spec:display-property:265c04 - initial letter exclusion area must continue into subsequent blocks when paragraph is shorter than drop cap
// +spec:text-alignment-spacing:c8a926 - order of operations: shaping → letter/word-spacing → justification → alignment
// +spec:text-alignment-spacing:13b72d - line box start/end determined by inline base direction
// +spec:text-alignment-spacing:d497af - line box inline base direction affects text-align resolution
// +spec:text-alignment-spacing:68332e - bidi direction determines start/end to left/right mapping
// +spec:box-model:d781f3 - empty line boxes (no text, no preserved whitespace, no inline elements with non-zero margins/padding/borders, no in-flow content) are treated as zero-height
// +spec:display-property:90d782 - Phantom line boxes (containing only empty inline boxes, out-of-flow items, or collapsed whitespace) are ignored
// +spec:line-height:9ca9d9 - line box height = distance from uppermost box top to lowermost box bottom, including strut
let (content_ascent, content_descent) = calculate_line_metrics(&line_items, constraints.vertical_align, constraints);
// +spec:box-model:e99f7d - strut: each line box starts with zero-width inline box with block container's font/line-height
// +spec:line-height:29c478 - strut: zero-width inline box with block container's font/line-height
// +spec:text-alignment-spacing:b9d88e - justify stretches inline boxes via text-justify; non-collapsible WS may skip justification
// +spec:text-alignment-spacing:30d322 - justify lines with justification opportunities when text-align is justify
let (extra_word_spacing, extra_char_spacing) = if (constraints.text_align == TextAlign::Justify
// +spec:line-breaking:155a96 - pre-wrap hanging spaces: unconditionally hang without forced break, conditionally hang with forced break
// +spec:white-space-processing:68af09 - Phase II: trailing whitespace hanging/conditional hanging per white-space mode
// +spec:white-space-processing:75d91e - preserved white space hangs at line end, affecting intrinsic sizing
// +spec:intrinsic-sizing:1db683 - conditionally hanging glyphs excluded from min-content, included in max-content
// +spec:display-contents:2704a2 - conditionally hanging chars not considered when measuring line fit
// +spec:text-alignment-spacing:287316 - overflow content is start-aligned; alignment offset within line box
// +spec:inline-formatting-context:267438 - Content positioning: position aligned subtree and baseline-shift values within line box
// +spec:font-metrics:f29b61 - baseline alignment matches corresponding baseline types (only alphabetic implemented)
// +spec:block-formatting-context:26b535 - In vertical typographic mode, central baseline is dominant when text-orientation is mixed/upright; otherwise alphabetic
// +spec:inline-formatting-context:eb735b - alignment-baseline: inline-level boxes aligned to parent's baseline via vertical-align
// +spec:inline-formatting-context:da3f34 - baseline alignment of in-flow inline-level boxes in block axis per dominant-baseline/vertical-align
// +spec:display-property:328cfc - baseline-shift / aligned subtree vertical alignment (sub, super, top, bottom, center)
// +spec:line-height:0fcfab - vertical-align property values (baseline, top, middle, bottom, sub, super, text-top, text-bottom, percentage, length)
// +spec:display-property:8e018d - aligned subtree edges used for top/bottom line box alignment
// +spec:inline-formatting-context:495672 - line-relative vertical-align (top/center/bottom) and aligned subtree positioning
// +spec:font-metrics:70000d - align vertical midpoint of box with baseline + half x-height of parent
// +spec:display-property:3b0e76 - baseline-shift super raises by ~1/3 font-size; top/bottom align to line box edges
// +spec:display-property:8bf37e - dominant-baseline defaults to alphabetic; baseline alignment matches parent
// +spec:font-metrics:96bbd3 - baseline: align alphabetic baseline of box with parent's alphabetic baseline
// +spec:text-alignment-spacing:e09bd1 - justification space added on top of letter-spacing/word-spacing
let is_cursive = if let ShapedItem::Cluster(c) = &item { is_cursive_script_cluster(c) } else { false };
if !is_outside_marker && extra_char_spacing > 0.0 && can_justify_after(&item) && !is_cursive {
// +spec:display-property:3a833c - consecutive atomic inlines treated as single unit for letter-spacing
// +spec:text-alignment-spacing:22bea4 - letter-spacing applied after bidi reordering, additive with kerning and word-spacing; justification may further adjust
// +spec:overflow:e63bc0 - letter-spacing ignores zero-width formatting chars (Cf); handled by shaper merging them into clusters
// +spec:text-alignment-spacing:80f9ec - letter-spacing applied per-cluster using innermost element's style (UA-allowed attachment)
// +spec:text-alignment-spacing:bdd704 - letter-spacing applied after each cluster, not at line start
// +spec:text-alignment-spacing:d3ef6e - single-char element: only trailing space, no inter-char effect
// +spec:text-alignment-spacing:d668fc - letter-spacing only affects characters within the element (per-cluster style)
// +spec:text-alignment-spacing:8dbb78 - zero letter-spacing behaves as normal (Px(0) adds no spacing)
// +spec:width-calculation:9447d1 - word-spacing only applied to word separators; zero-width chars like U+200B are excluded
// +spec:display-contents:654278 - distributes remaining space to fill line box when justifying
// +spec:text-alignment-spacing:56c7f4 - equal distribution of justification space within priority level
// +spec:text-alignment-spacing:f17bbc - justification opportunities controlled by text-justify value (inter-word = word separators, inter-character = character juxtaposition)
// +spec:text-alignment-spacing:71314a - script categories for justification: inter-word for clustered, kashida for cursive (Arabic), inter-character for block (CJK)
// +spec:margin-collapsing:6706c1 - fixed-width spaces (U+2000–U+200A, U+3000) excluded from word separators
// +spec:line-breaking:fd3164 - U+200B as explicit word delimiter for scripts without space-separated words
// +spec:font-metrics:b8eb97 - Script group classification for justification/letter-spacing behavior
// +spec:overflow:b06c3e - text overflows when wrapping is prevented (e.g. white-space: nowrap)
/// Computes horizontal line segments where a polygon intersects a scanline at the given y range.
// +spec:inline-block:6e7dd9 - Non-tailorable Unicode line breaking controls take precedence over atomic inline rules (CSS-TEXT-3 recent changes, issue 8972)
// +spec:line-breaking:495247 - CJK/syllabic writing systems allow breaks between typographic letter units with varying strictness
// +spec:line-breaking:31ef1a - word-break property controls soft wrap opportunities between letters (NU/AL/AI/ID classes as letter units)
// +spec:line-breaking:798252 - word-break property affects break opportunities (normal/break-all/keep-all)
// +spec:line-breaking:8fed57 - word-break: break-all treats all clusters as break opportunities, keep-all suppresses CJK breaks
// +spec:line-breaking:e2b374 - word-break: normal (only at separators) vs break-all (between all letters incl. Ethiopic)
// +spec:overflow:53a97f - word-break (normal/break-all/keep-all) and line-break strictness rules
// +spec:line-breaking:a75147 - word-break property: normal (CJK breaks), break-all (every cluster), keep-all (suppress CJK breaks)
// +spec:line-breaking:7eca16 - U+200B ZERO WIDTH SPACE is always a break opportunity, even with keep-all
fn is_break_opportunity_with_word_break(item: &ShapedItem, word_break: WordBreak, hyphens: Hyphens) -> bool {
// +spec:line-breaking:432d5b - hyphens property controls soft wrap opportunities via hyphenation
// +spec:line-breaking:5a32a1 - soft hyphen (U+00AD) creates break opportunity; glyph styled per surrounding text properties
// +spec:line-breaking:2bbda0 - word-break does not affect soft wrap opportunities around punctuation
// +spec:line-breaking:db0289 - line-break strictness: anywhere allows soft wrap around every typographic character unit
// +spec:line-breaking:7d242b - line-break strictness levels: loose/normal/strict/anywhere with CJK punctuation rules
// +spec:line-breaking:67bfe8 - line-break strictness (auto/loose/normal/strict/anywhere) controls
// - normal: allows breaks before small kana (CJ); allows CJK hyphen breaks for CJK writing systems
/// Returns true if the character is a Japanese small kana or Katakana-Hiragana prolonged sound mark
// +spec:line-breaking:05e09a - U+002D/U+2010 always create soft wrap opportunities regardless of hyphens property
// a single typographic character unit (every character is a soft wrap opportunity), including
if is_break_opportunity_with_word_break(&source_items[0], self.word_break, self.hyphens) {
if i > 0 && !suppress_next_break && !starts_with_suppress && !cjk_strictness_suppressed && is_break_opportunity_with_word_break(item, self.word_break, self.hyphens) {