@a3s-lab/office 0.2.1 → 0.2.2

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.
package/README.md CHANGED
@@ -307,10 +307,13 @@ interaction model.
307
307
  gaps, centered fixed table, row heights, borders, fills, and cell margins
308
308
  stay within the checked one-pixel layout-landmark budget while preserving
309
309
  the original OOXML line multiples for DOCX export.
310
- Separate 30-row common-font, 36-row CJK-font, and 18-row document-grid
311
- matrices now gate text-band positions against WPS. The browser keeps the
312
- measured per-font automatic-line advance, while DOCX round trips retain the
313
- section `docGrid` type and line pitch and each run's `snapToGrid` override.
310
+ Separate 30-row common-font, 36-row CJK-font, 18-row document-grid, and
311
+ 30-row multilingual-script matrices now gate text-band positions against
312
+ WPS. DOCX import selects the Word `ascii`, `hAnsi`, `eastAsia`, or `cs` font
313
+ slot from the run script, including complex-script bold, italic, size, and
314
+ RTL overrides. The browser keeps the measured per-font automatic-line
315
+ advance, while DOCX round trips retain the section `docGrid` type and line
316
+ pitch and each run's `snapToGrid` override.
314
317
  _DOCX import/export; PDF export._
315
318
  - **Markdown** — GFM source, visual editing, synchronized and resizable split
316
319
  preview, source-native undo/redo with typing coalescing and selection restore,
@@ -2297,7 +2297,7 @@ function markDocxRunFormatting(document, styleSource, themeSource, tableStyleSou
2297
2297
  if (!paragraph) continue;
2298
2298
  const paragraphProperties = directChild(paragraph, 'pPr');
2299
2299
  const runProperties = directChild(run, 'rPr');
2300
- const formatting = resolvedRunFormatting(docxRunPropertySources(paragraphProperties, runProperties, styles, docxTableRunPropertySources(run, tableStyles)), theme);
2300
+ const formatting = resolvedRunFormatting(docxRunPropertySources(paragraphProperties, runProperties, styles, docxTableRunPropertySources(run, tableStyles)), theme, runText);
2301
2301
  if (!Object.keys(formatting).length) continue;
2302
2302
  const index = runs.length + 1;
2303
2303
  const startMarker = `__A3S_WORK_RUN_START_${index}__`;
@@ -2325,25 +2325,36 @@ function applyImportedDocxRunFormattingMarkers(document, markers) {
2325
2325
  function hasImportedDocxRunFormattingMarkers(markers) {
2326
2326
  return markers.runs.length > 0;
2327
2327
  }
2328
- function resolvedRunFormatting(propertySources, theme) {
2328
+ function resolvedRunFormatting(propertySources, theme, runText) {
2329
2329
  const fonts = {};
2330
2330
  let fontSize;
2331
+ let complexFontSize;
2331
2332
  let color;
2332
2333
  let backgroundColor;
2333
2334
  let bold;
2335
+ let complexBold;
2334
2336
  let italic;
2337
+ let complexItalic;
2335
2338
  let underline;
2336
2339
  let strike;
2337
2340
  let snapToGrid;
2341
+ let complexScriptFormatting;
2342
+ let rightToLeft;
2343
+ let fontHint;
2338
2344
  for (const properties of propertySources){
2339
2345
  bold = overriddenBoolean(bold, onOffProperty(properties, 'b'));
2346
+ complexBold = overriddenBoolean(complexBold, onOffProperty(properties, 'bCs'));
2340
2347
  italic = overriddenBoolean(italic, onOffProperty(properties, 'i'));
2348
+ complexItalic = overriddenBoolean(complexItalic, onOffProperty(properties, 'iCs'));
2341
2349
  underline = overriddenBoolean(underline, underlineProperty(properties));
2342
2350
  strike = overriddenBoolean(strike, onOffProperty(properties, 'strike'));
2343
2351
  strike = overriddenBoolean(strike, onOffProperty(properties, 'dstrike'));
2344
2352
  snapToGrid = overriddenBoolean(snapToGrid, onOffProperty(properties, 'snapToGrid'));
2353
+ complexScriptFormatting = overriddenBoolean(complexScriptFormatting, onOffProperty(properties, 'cs'));
2354
+ rightToLeft = overriddenBoolean(rightToLeft, onOffProperty(properties, 'rtl'));
2345
2355
  const runFonts = directChild(properties, 'rFonts');
2346
2356
  if (runFonts) {
2357
+ fontHint = resolvedFontHint(runFonts) ?? fontHint;
2347
2358
  assignFont(fonts, 'ascii', resolvedFont(runFonts, 'ascii', 'asciiTheme', theme));
2348
2359
  assignFont(fonts, 'hAnsi', resolvedFont(runFonts, 'hAnsi', 'hAnsiTheme', theme));
2349
2360
  assignFont(fonts, 'eastAsia', resolvedFont(runFonts, 'eastAsia', 'eastAsiaTheme', theme));
@@ -2351,6 +2362,8 @@ function resolvedRunFormatting(propertySources, theme) {
2351
2362
  }
2352
2363
  const size = work_docx_run_formatting_import_numericAttribute(directChild(properties, 'sz'), 'val');
2353
2364
  if (void 0 !== size && size > 0) fontSize = Math.min(512, size / 2);
2365
+ const complexSize = work_docx_run_formatting_import_numericAttribute(directChild(properties, 'szCs'), 'val');
2366
+ if (void 0 !== complexSize && complexSize > 0) complexFontSize = Math.min(512, complexSize / 2);
2354
2367
  const colorElement = directChild(properties, 'color');
2355
2368
  if (colorElement) {
2356
2369
  const value = work_docx_run_formatting_import_wordAttribute(colorElement, 'val')?.trim();
@@ -2372,25 +2385,25 @@ function resolvedRunFormatting(propertySources, theme) {
2372
2385
  else if (fill && /^[0-9a-f]{6}$/i.test(fill)) backgroundColor = `#${fill.toLowerCase()}`;
2373
2386
  }
2374
2387
  }
2375
- let fontFamily = uniqueFonts([
2376
- fonts.eastAsia,
2377
- fonts.hAnsi,
2378
- fonts.ascii,
2379
- fonts.complex
2380
- ]);
2381
- if (!fontFamily) fontFamily = uniqueFonts([
2382
- docxThemeFont(theme, 'minorEastAsia'),
2383
- docxThemeFont(theme, 'minorHAnsi'),
2384
- docxThemeFont(theme, 'minorAscii'),
2385
- docxThemeFont(theme, 'minorBidi')
2386
- ]);
2388
+ const fontSlot = true === complexScriptFormatting || true === rightToLeft ? 'complex' : docxFontSlotForText(runText, fontHint);
2389
+ const usesComplexFormatting = 'complex' === fontSlot;
2390
+ const resolvedBold = usesComplexFormatting ? complexBold ?? bold : bold;
2391
+ const resolvedItalic = usesComplexFormatting ? complexItalic ?? italic : italic;
2392
+ const resolvedFontSize = usesComplexFormatting ? complexFontSize ?? fontSize : fontSize;
2393
+ let fontFamily = uniqueFonts(orderedFonts(fonts, fontSlot));
2394
+ if (!fontFamily) fontFamily = uniqueFonts(orderedFonts({
2395
+ ascii: docxThemeFont(theme, 'minorAscii'),
2396
+ hAnsi: docxThemeFont(theme, 'minorHAnsi'),
2397
+ eastAsia: docxThemeFont(theme, 'minorEastAsia'),
2398
+ complex: docxThemeFont(theme, 'minorBidi')
2399
+ }, fontSlot));
2387
2400
  const hasRunPropertySource = propertySources.length > 0;
2388
2401
  return {
2389
- ...void 0 !== bold || hasRunPropertySource ? {
2390
- bold: bold ?? false
2402
+ ...void 0 !== resolvedBold || hasRunPropertySource ? {
2403
+ bold: resolvedBold ?? false
2391
2404
  } : {},
2392
- ...void 0 !== italic || hasRunPropertySource ? {
2393
- italic: italic ?? false
2405
+ ...void 0 !== resolvedItalic || hasRunPropertySource ? {
2406
+ italic: resolvedItalic ?? false
2394
2407
  } : {},
2395
2408
  ...void 0 !== underline || hasRunPropertySource ? {
2396
2409
  underline: underline ?? false
@@ -2402,8 +2415,8 @@ function resolvedRunFormatting(propertySources, theme) {
2402
2415
  fontFamily,
2403
2416
  wordLineHeightFactor: documentWordLineHeightFactor(fontFamily)
2404
2417
  } : {},
2405
- ...void 0 !== fontSize ? {
2406
- fontSize
2418
+ ...void 0 !== resolvedFontSize ? {
2419
+ fontSize: resolvedFontSize
2407
2420
  } : {},
2408
2421
  ...void 0 !== snapToGrid ? {
2409
2422
  wordSnapToGrid: snapToGrid
@@ -2495,6 +2508,61 @@ function assignFont(fonts, key, value) {
2495
2508
  const normalized = value?.trim();
2496
2509
  if (normalized) fonts[key] = normalized;
2497
2510
  }
2511
+ function resolvedFontHint(element) {
2512
+ const hint = work_docx_run_formatting_import_wordAttribute(element, 'hint')?.trim().toLowerCase();
2513
+ if ('eastasia' === hint) return 'eastAsia';
2514
+ if ('cs' === hint) return 'complex';
2515
+ if ('default' === hint) return 'ascii';
2516
+ }
2517
+ function orderedFonts(fonts, preferred) {
2518
+ const order = {
2519
+ ascii: [
2520
+ 'ascii',
2521
+ 'hAnsi',
2522
+ 'eastAsia',
2523
+ 'complex'
2524
+ ],
2525
+ hAnsi: [
2526
+ 'hAnsi',
2527
+ 'ascii',
2528
+ 'eastAsia',
2529
+ 'complex'
2530
+ ],
2531
+ eastAsia: [
2532
+ 'eastAsia',
2533
+ 'hAnsi',
2534
+ 'ascii',
2535
+ 'complex'
2536
+ ],
2537
+ complex: [
2538
+ 'complex',
2539
+ 'hAnsi',
2540
+ 'ascii',
2541
+ 'eastAsia'
2542
+ ]
2543
+ };
2544
+ return order[preferred].map((slot)=>fonts[slot]);
2545
+ }
2546
+ function docxFontSlotForText(text, hint) {
2547
+ for (const character of text){
2548
+ const codePoint = character.codePointAt(0);
2549
+ if (!(void 0 === codePoint || isNeutralCodePoint(codePoint))) {
2550
+ if (isComplexScriptCodePoint(codePoint)) return 'complex';
2551
+ if (isEastAsianCodePoint(codePoint)) return 'eastAsia';
2552
+ return codePoint <= 0x7f ? 'ascii' : 'hAnsi';
2553
+ }
2554
+ }
2555
+ return hint ?? 'ascii';
2556
+ }
2557
+ function isNeutralCodePoint(codePoint) {
2558
+ return codePoint <= 0x20 || codePoint <= 0x7f && !(codePoint >= 0x41 && codePoint <= 0x5a || codePoint >= 0x61 && codePoint <= 0x7a) || codePoint >= 0x300 && codePoint <= 0x36f || codePoint >= 0x2000 && codePoint <= 0x206f;
2559
+ }
2560
+ function isComplexScriptCodePoint(codePoint) {
2561
+ return codePoint >= 0x0590 && codePoint <= 0x08ff || codePoint >= 0xfb1d && codePoint <= 0xfdff || codePoint >= 0xfe70 && codePoint <= 0xfeff || codePoint >= 0x1ee00 && codePoint <= 0x1eeff;
2562
+ }
2563
+ function isEastAsianCodePoint(codePoint) {
2564
+ return codePoint >= 0x1100 && codePoint <= 0x11ff || codePoint >= 0x2e80 && codePoint <= 0xa4cf || codePoint >= 0xac00 && codePoint <= 0xd7af || codePoint >= 0xf900 && codePoint <= 0xfaff || codePoint >= 0xfe10 && codePoint <= 0xfe6f || codePoint >= 0xff00 && codePoint <= 0xffef || codePoint >= 0x20000 && codePoint <= 0x323af;
2565
+ }
2498
2566
  function resolvedFont(element, explicitName, themeName, theme) {
2499
2567
  const explicit = work_docx_run_formatting_import_wordAttribute(element, explicitName);
2500
2568
  if (explicit?.trim()) return explicit;
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a3s-lab/office",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Open-source browser editors for documents, Markdown, spreadsheets, presentations, and PDFs.",
5
5
  "keywords": [
6
6
  "office",