Fix LT-22351: load default font features in BaseStyleInfo.ProcessStyleRules#388
Fix LT-22351: load default font features in BaseStyleInfo.ProcessStyleRules#388johnml1135 wants to merge 4 commits into
Conversation
…eRules BaseStyleInfo.ProcessStyleRules iterates the string properties of a style's TsTextProps and handles ktptFontFamily, ktptBulNumTxtBef/Aft, ktptCustomBullet, ktptBulNumFontInfo, and ktptWsStyle, but had no case for ktptFontVariations (font features). As a result, font features set as a style-level default (not a per-writing-system override) fell through to the default: break and were silently dropped when the style was loaded/reloaded. The save path already writes ktptFontVariations for style defaults (see GetOverridesString), and SetFontStringProp already knows how to apply it to a FontInfo -- it's just never called for the default font info, only for per-WS overrides via MakeFontWsOverrides. This created a save/load asymmetry: a user could set font features as a style default via the Styles dialog, save, and have them vanish on reload. Fix: add a case for ktptFontVariations in the string-prop switch in ProcessStyleRules that sets m_defaultFontInfo.m_features.ExplicitValue, mirroring how ktptFontFamily sets m_fontName. Added BaseStyleInfoTests.ConstructBasedOnStyle_FontFeatures, which builds style rules with ktptFontVariations set as a default (no WS override) and asserts FontInfoForWs(-1).m_features.IsExplicit is true with the value round-tripping exactly. Confirmed the test fails before the fix (IsExplicit false) and passes after, with no regressions in the broader DomainServices test suite (488 passed / 0 failed on both net462 and net8.0). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Companion FieldWorks PR consuming this fix: sillsdev/FieldWorks#1005 (draft, pending a liblcm release containing this change). |
Review follow-ups on the ktptFontVariations load fix: Loading font features into the default font info made FontInfo.IsAnyExplicit true for styles whose only explicit style-level property is font features. That newly triggered the branch in SetNonExplicitPropertiesToInherited that pushes the default font info into each ws-specific override via InheritAllProperties -- AFTER the override had already inherited the based-on style's corresponding ws override. Since InheritValue overwrites the value of every still-inherited property (last caller wins), the second pass clobbered what the first pass set: e.g. a based-on style with ws override FontName=Fontastic for English but style-level default Times New Roman would flip a child style's English override font to Times New Roman when the child's only explicit default was font features. Fix: add FontInfo.InheritExplicitProperties, which propagates only the explicitly-set properties of the source font info, and use it for the default-into-override pass. The default's other values were merely inherited from the based-on style's default and must not overwrite values just inherited from the based-on style's ws override. Note this intentionally changes long-standing behavior for styles with any explicit default font property (e.g. an explicit default font name): previously ALL still-inherited override properties were overwritten with the default font info's values; now only the explicitly-set default properties are propagated. The old behavior was the same clobber bug reached an older way. Also per review: - ProcessStyleRules now delegates the ktptFontVariations case to SetFontStringProp, keeping the tag-to-field mapping in one place (mirrors how the int-prop loop delegates to SetExplicitFontIntProp). - ConstructBasedOnStyle_FontFeatures now asserts the font family it sets up, matching the sibling ConstructBasedOnStyle test. Added SetBasedOnStyleAndInheritValues_ExplicitDefaultFeaturesDoNot- ClobberWsOverride, which fails before the fix (English override font "Times New Roman" instead of "Fontastic") and passes after. Full solution test suite is green on net462 and net8.0 (SIL.LCModel.Tests 1704 passed, Core.Tests 787, Utils.Tests 302, FixData.Tests 21). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tring prop In BulletInfo.DecodeFontInfo, the ktptFontFamily and ktptFontVariations branches ended with loop-level break statements, so decoding stopped entirely at the first string property encountered. EncodeFontInfo always writes the font family before the font variations, which meant a bullet font with both a font name and font features lost its features on every decode -- another save/load asymmetry for ktptFontVariations. Fix: continue the loop after handling each string property instead of breaking out of it. The manual index advancement past the property's terminating NUL already leaves the index at the next property, so the int-prop parsing below is unaffected. Added BulletInfoTests.RoundTripEncodingAndDecodingOfFontInfo_FontName- AndFontFeatures, an encode/decode round trip with both m_fontName and m_features explicit. It fails before the fix (features not explicit after decode) and passes after, along with the rest of BulletInfoTests and the full solution test suite on net462 and net8.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pin the intended behavior of the IsAnyExplicit block that commit 113a768 (Fix for LT-18109 - Inherit writing system overrides, 2017) added to SetNonExplicitPropertiesToInherited, proving that the InheritExplicitProperties change preserves it: - SetBasedOnStyleAndInheritValues_ExplicitDefaultPropsPropagateInto- WsOverride: a child style's explicit style-level default (bold) shows through in its ws override, while the font name the child does not set explicitly keeps the value inherited from the parent's ws override. - SetBasedOnStyleAndInheritValues_ExplicitDefaultFontNameBeatsParent- WsOverride: a child style's explicit style-level default font name applies across writing systems and wins over the parent's ws override, per the LT-18109 commit message ("If the default font info in a style specifies overrides the defaults overrule the parent style ws overrides"). Together with SetBasedOnStyleAndInheritValues_ExplicitDefaultFeatures- DoNotClobberWsOverride these pin all three semantics: no clobber of inherited ws-override values by merely-inherited defaults, propagation of explicit defaults into ws overrides, and explicit-default precedence over the parent's ws override. Both new tests pass with no production change; full solution suite green on net462 and net8.0 (SIL.LCModel.Tests 1706 passed / 0 failed / 18 skipped, Core.Tests 787/787, Utils.Tests 302 passed / 2 skipped, FixData.Tests 21/21). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Regarding the question of whether the original one-line load fix was preferable to the follow-up inheritance change: the PR description now has a dedicated subsection, "Why the inheritance change is needed (v1 vs v2)", laying out the evidence -- including why the one-line fix alone regresses ws-override inheritance for styles whose only style-level setting is font features, how the change preserves the intent of the original LT-18109 commit (113a768), and the alternative that was considered and rejected. Commit 66eda1b adds two tests that pin the LT-18109 semantics under the new code (explicit defaults propagate into ws overrides, and an explicit default font name wins over the parent's ws override); both pass with no further production change, and the full suite is green on net462 and net8.0. |
…feature precedence Review follow-ups on the LT-22351 branch: - Widen SafelyAddStyleToSheetAndTable to accept BaseStyleInfo (both collections it touches already store BaseStyleInfo) and add a matching SafelyRemoveStyleFromSheetAndTable helper. - The persisted-Rules preview test now uses those helpers and removes its style in a finally block: m_styleSheet is per-fixture but the base class undoes all data changes per test, so leaving a BaseStyleInfo wrapping a real IStStyle behind would strand a stale entry with a dead RealStyle that can crash later tests order-dependently. - Add GenerateCssForConfiguration_NormalStyleOwnFontFeatures_ BeatWritingSystemDefaultFontFeatures: now that liblcm loads a style's own default ktptFontVariations from persisted Rules (sillsdev/liblcm#388), a Normal style WITH its own features must win over the writing system's DefaultFontFeatures fallback in AddFontInfoCss; the existing sibling test only covered Normal WITHOUT its own features. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… and precedence The Word export path (WordStylesGenerator.AddFontInfoWordStyles) has the same precedence logic as the CSS path but had no coverage through the authoritative liblcm load path: the existing typography test used a TestStyle double with an in-memory ExplicitValue (bypassing BaseStyleInfo.ProcessStyleRules) and the existing Normal-style test only covered the writing-system DefaultFontFeatures fallback branch. Add two tests mirroring the CSS-side coverage: - GenerateCharacterStyleFromLcmStyleSheet_DefaultFontFeaturesFromPersisted StyleRules_AddsWordTypographyProperties: a real IStStyle whose persisted Rules carry ktptFontVariations, wrapped in a plain BaseStyleInfo the way LcmStyleSheet does in production, produces the expected w14 typography properties (sillsdev/liblcm#388). - GenerateCharacterStyleFromLcmStyleSheet_NormalStyleOwnFontFeatures_Beat WritingSystemDefaultFontFeatures: a Normal style with its own persisted features wins over the WS DefaultFontFeatures fallback (font family still falls back to the WS default font). Both tests clean up in finally blocks so no stale entry wrapping an undone IStStyle is left in the fixture-lifetime stylesheet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
design.md Decision 11, proposal.md, and research.md still described StyleInfo.LoadDefaultFontFeatures as an existing, load-bearing compatibility adapter. Update those passages to record that the adapter was removed under LT-22351 once liblcm's BaseStyleInfo.ProcessStyleRules gained the ktptFontVariations case (sillsdev/liblcm#388) and the Decision 11 gating condition (SaveToDB_DefaultFontFeatures_RoundTripsThroughRules passing through the authoritative path) was satisfied. Surgical wording updates only; no restructuring. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
liblcm's BaseStyleInfo.ProcessStyleRules previously dropped a style's default ktptFontVariations (font features) when loading from persisted Rules (sillsdev/liblcm#388). FieldWorks compensated for this in the Styles dialog only, via StyleInfo.LoadDefaultFontFeatures, which manually rescanned style.Rules for ktptFontVariations after calling the base constructor. The Dictionary Preview CSS path (CssGenerator.GenerateCssStyleFromLcmStyleSheet -> AddFontInfoCss) never went through StyleInfo -- it reads plain BaseStyleInfo objects straight out of LcmStyleSheet -- so it had no such adapter and silently lost default font features, meaning font-feature-settings never reached the generated preview CSS. Now that liblcm loads default font features itself, remove the FieldWorks-side adapter and let StyleInfo(IStStyle) rely entirely on BaseStyleInfo.ProcessStyleRules, as the LT-22324 design doc intended once the round-trip test passed through the authoritative liblcm path. Add a CssGenerator regression test that builds a style the way LcmStyleSheet does in production (a real IStStyle with persisted Rules, wrapped in BaseStyleInfo -- not a TestStyle double with an in-memory ExplicitValue) and asserts that default font features reach the generated CSS end-to-end. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review follow-ups on the LT-22351 branch: - Widen SafelyAddStyleToSheetAndTable to accept BaseStyleInfo (both collections it touches already store BaseStyleInfo) and add a matching SafelyRemoveStyleFromSheetAndTable helper. - The persisted-Rules preview test now uses those helpers and removes its style in a finally block: m_styleSheet is per-fixture but the base class undoes all data changes per test, so leaving a BaseStyleInfo wrapping a real IStStyle behind would strand a stale entry with a dead RealStyle that can crash later tests order-dependently. - Add GenerateCssForConfiguration_NormalStyleOwnFontFeatures_ BeatWritingSystemDefaultFontFeatures: now that liblcm loads a style's own default ktptFontVariations from persisted Rules (sillsdev/liblcm#388), a Normal style WITH its own features must win over the writing system's DefaultFontFeatures fallback in AddFontInfoCss; the existing sibling test only covered Normal WITHOUT its own features. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Word export path (WordStylesGenerator.AddFontInfoWordStyles) has the same precedence logic as the CSS path but had no coverage through the authoritative liblcm load path: the existing typography test used a TestStyle double with an in-memory ExplicitValue (bypassing BaseStyleInfo.ProcessStyleRules) and the existing Normal-style test only covered the writing-system DefaultFontFeatures fallback branch. Add two tests mirroring the CSS-side coverage: - GenerateCharacterStyleFromLcmStyleSheet_DefaultFontFeaturesFromPersisted StyleRules_AddsWordTypographyProperties: a real IStStyle whose persisted Rules carry ktptFontVariations, wrapped in a plain BaseStyleInfo the way LcmStyleSheet does in production, produces the expected w14 typography properties (sillsdev/liblcm#388). - GenerateCharacterStyleFromLcmStyleSheet_NormalStyleOwnFontFeatures_Beat WritingSystemDefaultFontFeatures: a Normal style with its own persisted features wins over the WS DefaultFontFeatures fallback (font family still falls back to the WS default font). Both tests clean up in finally blocks so no stale entry wrapping an undone IStStyle is left in the fixture-lifetime stylesheet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
design.md Decision 11, proposal.md, and research.md still described StyleInfo.LoadDefaultFontFeatures as an existing, load-bearing compatibility adapter. Update those passages to record that the adapter was removed under LT-22351 once liblcm's BaseStyleInfo.ProcessStyleRules gained the ktptFontVariations case (sillsdev/liblcm#388) and the Decision 11 gating condition (SaveToDB_DefaultFontFeatures_RoundTripsThroughRules passing through the authoritative path) was satisfied. Surgical wording updates only; no restructuring. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jasonleenaylor
left a comment
There was a problem hiding this comment.
Make sure that we squash with a clean and concise commit comment describing the 3 different changes in appropriate detail.
@jasonleenaylor reviewed 6 files and all commit messages, and made 1 comment.
Reviewable status:complete! all files reviewed, all discussions resolved (waiting on johnml1135).
Summary
Font features set as a style's default font (not tied to a writing system) saved correctly but silently disappeared on reload — breaking Dictionary Preview CSS generation for anyone using default-level font features.
1. The bug that started it all
flowchart LR A["Styles dialog<br/>sets default font features"] --> B["Save<br/>GetOverridesString serializes fine"] B --> C[("Database<br/>style rules blob")] C --> D["Load<br/>ProcessStyleRules switch"] D -->|"no case for ktptFontVariations"| E["Dropped"]BaseStyleInfo.ProcessStyleRuleshad a case for the default font name but none for default font features (ktptFontVariations), so it fell todefault: breakand the value was thrown away on load. The save path and the per-writing-system override path both already understood this property; only the default-font load path didn't.2. Fixing it exposed a second, older bug
Take a parent style
Title Mainwith an English override ofFontasticand a default font ofTimes New Roman. A child styleInherit Titleis based on it, and the only thing set on the child is a default font feature:smcp=1. What does the child's English text render in?InheritAllProperties(default)— copies every default value, explicit or notInheritExplicitProperties(default)— copies only values the default itself set explicitlyThe middle column isn't hypothetical — it's what a one-line fix to just
ProcessStyleRuleswould have produced, since fixing the load path is what makesIsAnyExplicittrue and turns on the propagate-to-overrides branch for the first time for these styles.Fix: only propagate default properties the style explicitly set, via new
FontInfo.InheritExplicitProperties. Properties the default merely inherited from further up the style chain are left alone, so they no longer clobber an override's own, more specific inheritance.3. The rule now, in plain terms
4. A third, smaller bug found along the way: bullet fonts
BulletInfo.DecodeFontInfostopped reading an encoded blob after the first string property, so a bullet with both a custom font name and font features lost the features on decode:break)continue)ktptFontFamily= "Algerian"ktptFontVariations= "smcp=1,ss01=2"One-character fix:
break→continue.Tests
Full suite green on net462 and net8.0 (SIL.LCModel.Tests 1706/1706, SIL.LCModel.Core.Tests 787/787, SIL.LCModel.Utils.Tests 302 passed/2 skipped, SIL.LCModel.FixData.Tests 21/21) — no regressions.
References JIRA LT-22351. A companion FieldWorks PR (#1005) will consume this liblcm fix once merged and released.
🤖 Generated with Claude Code
This change is