Mayo commited on
Commit
e42e58e
·
unverified ·
1 Parent(s): de517f0

fix: global font family & disable image transition

Browse files
koharu-renderer/src/facade.rs CHANGED
@@ -146,14 +146,8 @@ impl Renderer {
146
  text_align: None,
147
  });
148
 
149
- if let Some(ff) = font_family {
150
- if !style.font_families.contains(&ff.to_string()) {
151
- style.font_families.insert(0, ff.to_string());
152
- } else {
153
- style.font_families.retain(|x| x != ff);
154
- style.font_families.insert(0, ff.to_string());
155
- }
156
- }
157
  let font = self.select_font(&style)?;
158
  let block_effect = style.effect.unwrap_or(effect);
159
  let color = text_block
@@ -294,6 +288,20 @@ fn default_stroke_width(font_size: f32) -> f32 {
294
  (font_size * 0.10).clamp(1.2, 8.0)
295
  }
296
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  fn resolve_stroke_style(
298
  block: &TextBlock,
299
  block_stroke: Option<&TextStrokeStyle>,
@@ -427,7 +435,10 @@ fn load_symbol_fallbacks(fontbook: &mut FontBook) -> Vec<Font> {
427
 
428
  #[cfg(test)]
429
  mod tests {
430
- use super::{align_layout_horizontally, center_layout_vertically};
 
 
 
431
  use crate::layout::{LayoutLine, LayoutRun, WritingMode};
432
  use koharu_types::TextAlign;
433
 
@@ -534,4 +545,26 @@ mod tests {
534
  assert_eq!(layout.lines[0].baseline.1, 32.0);
535
  assert_eq!(layout.height, 60.0);
536
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
537
  }
 
146
  text_align: None,
147
  });
148
 
149
+ apply_global_font_family(&mut style.font_families, font_family);
150
+ apply_default_font_families(&mut style.font_families, &normalized_translation);
 
 
 
 
 
 
151
  let font = self.select_font(&style)?;
152
  let block_effect = style.effect.unwrap_or(effect);
153
  let color = text_block
 
288
  (font_size * 0.10).clamp(1.2, 8.0)
289
  }
290
 
291
+ fn apply_global_font_family(font_families: &mut Vec<String>, font_family: Option<&str>) {
292
+ if font_families.is_empty()
293
+ && let Some(font_family) = font_family
294
+ {
295
+ font_families.push(font_family.to_string());
296
+ }
297
+ }
298
+
299
+ fn apply_default_font_families(font_families: &mut Vec<String>, text: &str) {
300
+ if font_families.is_empty() {
301
+ *font_families = font_families_for_text(text);
302
+ }
303
+ }
304
+
305
  fn resolve_stroke_style(
306
  block: &TextBlock,
307
  block_stroke: Option<&TextStrokeStyle>,
 
435
 
436
  #[cfg(test)]
437
  mod tests {
438
+ use super::{
439
+ align_layout_horizontally, apply_default_font_families, apply_global_font_family,
440
+ center_layout_vertically,
441
+ };
442
  use crate::layout::{LayoutLine, LayoutRun, WritingMode};
443
  use koharu_types::TextAlign;
444
 
 
545
  assert_eq!(layout.lines[0].baseline.1, 32.0);
546
  assert_eq!(layout.height, 60.0);
547
  }
548
+
549
+ #[test]
550
+ fn explicit_block_font_should_not_be_overridden_by_global_font() {
551
+ let mut font_families = vec!["Block Font".to_string()];
552
+ apply_global_font_family(&mut font_families, Some("Global Font"));
553
+
554
+ assert_eq!(font_families, vec!["Block Font".to_string()]);
555
+ }
556
+
557
+ #[test]
558
+ fn global_font_should_fill_empty_block_font_list() {
559
+ let mut font_families = Vec::new();
560
+ apply_global_font_family(&mut font_families, Some("Global Font"));
561
+ assert_eq!(font_families, vec!["Global Font".to_string()]);
562
+ }
563
+
564
+ #[test]
565
+ fn default_font_families_should_fill_empty_list() {
566
+ let mut font_families = Vec::new();
567
+ apply_default_font_families(&mut font_families, "hello");
568
+ assert!(!font_families.is_empty());
569
+ }
570
  }
ui/components/canvas/Workspace.tsx CHANGED
@@ -299,6 +299,7 @@ export function Workspace() {
299
  data-testid='workspace-inpainted-image'
300
  data={currentDocument.inpainted}
301
  visible={showInpaintedImage}
 
302
  />
303
  )}
304
  <canvas
@@ -345,10 +346,11 @@ export function Workspace() {
345
  style={{ zIndex: 30 }}
346
  />
347
  )}
348
- {currentDocument?.rendered && showRenderedImage && (
349
  <Image
350
  data-testid='workspace-rendered-image'
351
- data={currentDocument?.rendered}
 
352
  style={{ zIndex: 40 }}
353
  />
354
  )}
 
299
  data-testid='workspace-inpainted-image'
300
  data={currentDocument.inpainted}
301
  visible={showInpaintedImage}
302
+ transition={false}
303
  />
304
  )}
305
  <canvas
 
346
  style={{ zIndex: 30 }}
347
  />
348
  )}
349
+ {currentDocument.rendered && showRenderedImage && (
350
  <Image
351
  data-testid='workspace-rendered-image'
352
+ data={currentDocument.rendered}
353
+ transition={false}
354
  style={{ zIndex: 40 }}
355
  />
356
  )}
ui/components/panels/RenderControlsPanel.tsx CHANGED
@@ -110,6 +110,27 @@ const normalizeStroke = (stroke?: Partial<RenderStroke>): RenderStroke => ({
110
  widthPx: stroke?.widthPx,
111
  })
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  const resolveEffectiveTextAlign = (
114
  block:
115
  | {
@@ -159,8 +180,8 @@ export function RenderControlsPanel() {
159
  ]
160
  const fontOptions = uniqueStrings(fontCandidates)
161
  const currentFont =
162
- fontFamily ??
163
  selectedBlock?.style?.fontFamilies?.[0] ??
 
164
  firstBlock?.style?.fontFamilies?.[0] ??
165
  (hasBlocks ? fallbackFontFamilies[0] : '')
166
  const currentEffect = normalizeEffect(
@@ -189,15 +210,35 @@ export function RenderControlsPanel() {
189
  const currentTextAlign = resolveEffectiveTextAlign(
190
  selectedBlock ?? firstBlock,
191
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
193
  const buildStyle = (
 
 
 
 
 
 
 
 
194
  style: TextStyle | undefined,
195
  updates: Partial<TextStyle>,
196
  ): TextStyle => ({
197
- fontFamilies:
198
- updates.fontFamilies ?? style?.fontFamilies ?? fallbackFontFamilies,
199
  fontSize: updates.fontSize ?? style?.fontSize,
200
- color: updates.color ?? style?.color ?? fallbackColor,
201
  effect: updates.effect ?? style?.effect,
202
  stroke: updates.stroke ?? style?.stroke,
203
  textAlign: updates.textAlign ?? style?.textAlign,
@@ -205,7 +246,7 @@ export function RenderControlsPanel() {
205
 
206
  const applyStyleToSelected = (updates: Partial<TextStyle>) => {
207
  if (selectedBlockIndex === undefined) return false
208
- const nextStyle = buildStyle(selectedBlock?.style, updates)
209
  void replaceBlock(selectedBlockIndex, { style: nextStyle })
210
  return true
211
  }
@@ -214,7 +255,7 @@ export function RenderControlsPanel() {
214
  if (!hasBlocks) return
215
  const nextBlocks = textBlocks.map((block) => ({
216
  ...block,
217
- style: buildStyle(block.style, updates),
218
  }))
219
  void updateTextBlocks(nextBlocks)
220
  }
@@ -273,6 +314,18 @@ export function RenderControlsPanel() {
273
 
274
  return (
275
  <div className='flex w-full min-w-0 flex-col gap-1.5'>
 
 
 
 
 
 
 
 
 
 
 
 
276
  <div className='grid w-full min-w-0 grid-cols-[3.5rem_minmax(0,1fr)] items-center gap-1.5'>
277
  <span className='text-muted-foreground text-[10px] font-medium tracking-wide uppercase'>
278
  {fontLabel}
@@ -283,16 +336,16 @@ export function RenderControlsPanel() {
283
  <Select
284
  value={currentFont}
285
  onValueChange={(value) => {
286
- setFontFamily(value)
287
  const nextFamilies = mergeFontFamilies(
288
  value,
289
  selectedBlock?.style?.fontFamilies,
290
  )
291
  if (applyStyleToSelected({ fontFamilies: nextFamilies })) return
 
292
  if (!hasBlocks) return
293
  const nextBlocks = textBlocks.map((block) => ({
294
  ...block,
295
- style: buildStyle(block.style, {
296
  fontFamilies: mergeFontFamilies(
297
  value,
298
  block.style?.fontFamilies,
 
110
  widthPx: stroke?.widthPx,
111
  })
112
 
113
+ const resolveStyleColor = (
114
+ style: TextStyle | undefined,
115
+ block:
116
+ | {
117
+ fontPrediction?: {
118
+ text_color: [number, number, number]
119
+ }
120
+ }
121
+ | undefined,
122
+ fallbackColor: RgbaColor,
123
+ ): RgbaColor =>
124
+ style?.color ??
125
+ (block?.fontPrediction?.text_color
126
+ ? [
127
+ block.fontPrediction.text_color[0],
128
+ block.fontPrediction.text_color[1],
129
+ block.fontPrediction.text_color[2],
130
+ 255,
131
+ ]
132
+ : fallbackColor)
133
+
134
  const resolveEffectiveTextAlign = (
135
  block:
136
  | {
 
180
  ]
181
  const fontOptions = uniqueStrings(fontCandidates)
182
  const currentFont =
 
183
  selectedBlock?.style?.fontFamilies?.[0] ??
184
+ fontFamily ??
185
  firstBlock?.style?.fontFamilies?.[0] ??
186
  (hasBlocks ? fallbackFontFamilies[0] : '')
187
  const currentEffect = normalizeEffect(
 
210
  const currentTextAlign = resolveEffectiveTextAlign(
211
  selectedBlock ?? firstBlock,
212
  )
213
+ const scopeLabel =
214
+ selectedBlockIndex !== undefined
215
+ ? t('render.fontScopeBlockIndex', {
216
+ index: selectedBlockIndex + 1,
217
+ defaultValue: `Block ${selectedBlockIndex + 1}`,
218
+ })
219
+ : t('render.fontScopeGlobal', {
220
+ defaultValue: 'Global',
221
+ })
222
+ const scopeToneClass =
223
+ selectedBlockIndex !== undefined
224
+ ? 'border-primary/20 bg-primary/10 text-primary'
225
+ : 'border-border/60 bg-muted text-muted-foreground'
226
 
227
  const buildStyle = (
228
+ block:
229
+ | {
230
+ style?: TextStyle
231
+ fontPrediction?: {
232
+ text_color: [number, number, number]
233
+ }
234
+ }
235
+ | undefined,
236
  style: TextStyle | undefined,
237
  updates: Partial<TextStyle>,
238
  ): TextStyle => ({
239
+ fontFamilies: updates.fontFamilies ?? style?.fontFamilies ?? [],
 
240
  fontSize: updates.fontSize ?? style?.fontSize,
241
+ color: updates.color ?? resolveStyleColor(style, block, fallbackColor),
242
  effect: updates.effect ?? style?.effect,
243
  stroke: updates.stroke ?? style?.stroke,
244
  textAlign: updates.textAlign ?? style?.textAlign,
 
246
 
247
  const applyStyleToSelected = (updates: Partial<TextStyle>) => {
248
  if (selectedBlockIndex === undefined) return false
249
+ const nextStyle = buildStyle(selectedBlock, selectedBlock?.style, updates)
250
  void replaceBlock(selectedBlockIndex, { style: nextStyle })
251
  return true
252
  }
 
255
  if (!hasBlocks) return
256
  const nextBlocks = textBlocks.map((block) => ({
257
  ...block,
258
+ style: buildStyle(block, block.style, updates),
259
  }))
260
  void updateTextBlocks(nextBlocks)
261
  }
 
314
 
315
  return (
316
  <div className='flex w-full min-w-0 flex-col gap-1.5'>
317
+ <div className='flex items-center justify-end'>
318
+ <span
319
+ data-testid='render-scope-indicator'
320
+ className={cn(
321
+ 'rounded-full border px-2 py-0.5 text-[10px] font-medium tracking-wide uppercase',
322
+ scopeToneClass,
323
+ )}
324
+ >
325
+ {scopeLabel}
326
+ </span>
327
+ </div>
328
+
329
  <div className='grid w-full min-w-0 grid-cols-[3.5rem_minmax(0,1fr)] items-center gap-1.5'>
330
  <span className='text-muted-foreground text-[10px] font-medium tracking-wide uppercase'>
331
  {fontLabel}
 
336
  <Select
337
  value={currentFont}
338
  onValueChange={(value) => {
 
339
  const nextFamilies = mergeFontFamilies(
340
  value,
341
  selectedBlock?.style?.fontFamilies,
342
  )
343
  if (applyStyleToSelected({ fontFamilies: nextFamilies })) return
344
+ setFontFamily(value)
345
  if (!hasBlocks) return
346
  const nextBlocks = textBlocks.map((block) => ({
347
  ...block,
348
+ style: buildStyle(block, block.style, {
349
  fontFamilies: mergeFontFamilies(
350
  value,
351
  block.style?.fontFamilies,