single-file-core 1.6.19 → 1.6.21

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/core/helper.js CHANGED
@@ -86,6 +86,17 @@ const FONT_WEIGHTS = {
86
86
  bolder: "700",
87
87
  lighter: "100"
88
88
  };
89
+ const FONT_STRETCHES = {
90
+ "ultra-condensed": "50%",
91
+ "extra-condensed": "62.5%",
92
+ "condensed": "75%",
93
+ "semi-condensed": "87.5%",
94
+ "normal": "100%",
95
+ "semi-expanded": "112.5%",
96
+ "expanded": "125%",
97
+ "extra-expanded": "150%",
98
+ "ultra-expanded": "200%"
99
+ };
89
100
  const COMMENT_HEADER_LEGACY = "Archive processed by SingleFile";
90
101
  const ELEMENT_NODE_TYPE = 1;
91
102
  const TEXT_NODE_TYPE = 3;
@@ -139,6 +150,8 @@ export {
139
150
  removeQuotes,
140
151
  flatten,
141
152
  getFontWeight,
153
+ getFontStretch,
154
+ getStylesheetsInCascadeOrder,
142
155
  normalizeFontFamily,
143
156
  getShadowRoot,
144
157
  appendInfobar,
@@ -926,8 +939,9 @@ function getUsedFont(computedStyle, usedFonts, usedFontsCharacters, drawnCharact
926
939
  if (fontFamilyName) {
927
940
  const fontWeight = getFontWeight(computedStyle.getPropertyValue("font-weight"));
928
941
  const fontVariant = computedStyle.getPropertyValue("font-variant") || "normal";
929
- const value = [fontFamilyName, fontWeight, fontStyle, fontVariant];
930
- usedFonts.set(JSON.stringify(value), [fontFamilyName, fontWeight, fontStyle, fontVariant]);
942
+ const fontStretch = getFontStretch(computedStyle.getPropertyValue("font-stretch") || "normal");
943
+ const value = [fontFamilyName, fontWeight, fontStyle, fontVariant, fontStretch];
944
+ usedFonts.set(JSON.stringify(value), value);
931
945
  if (usedFontsCharacters && drawnCharacters) {
932
946
  addUsedFontCharacters(usedFontsCharacters, fontFamilyName, fontStyle, drawnCharacters);
933
947
  }
@@ -1246,6 +1260,57 @@ function getFontWeight(weight) {
1246
1260
  return FONT_WEIGHTS[weight.toLowerCase().trim()] || weight;
1247
1261
  }
1248
1262
 
1263
+ function getFontStretch(stretch) {
1264
+ return FONT_STRETCHES[stretch] || stretch;
1265
+ }
1266
+
1267
+ function getStylesheetsInCascadeOrder(stylesheets) {
1268
+ const entries = [];
1269
+ const entriesByRules = new Map();
1270
+ stylesheets.forEach((stylesheetInfo, key) => {
1271
+ const entry = { stylesheetInfo, key, index: entries.length };
1272
+ entries.push(entry);
1273
+ if (stylesheetInfo.stylesheet && stylesheetInfo.stylesheet.children) {
1274
+ entriesByRules.set(stylesheetInfo.stylesheet.children, entry);
1275
+ }
1276
+ });
1277
+ const importedEntries = new Set();
1278
+ entries.forEach(entry => getImportedStylesheetEntries(entry, entriesByRules).forEach(importedEntry => importedEntries.add(importedEntry)));
1279
+ const orderedEntries = [];
1280
+ const visitedEntries = new Set();
1281
+ entries
1282
+ .filter(entry => !importedEntries.has(entry))
1283
+ .forEach(entry => addStylesheetEntry(entry, entriesByRules, orderedEntries, visitedEntries));
1284
+ entries
1285
+ .filter(entry => !visitedEntries.has(entry))
1286
+ .forEach(entry => addStylesheetEntry(entry, entriesByRules, orderedEntries, visitedEntries));
1287
+ return orderedEntries;
1288
+ }
1289
+
1290
+ function addStylesheetEntry(entry, entriesByRules, orderedEntries, visitedEntries) {
1291
+ if (!visitedEntries.has(entry)) {
1292
+ visitedEntries.add(entry);
1293
+ getImportedStylesheetEntries(entry, entriesByRules).forEach(importedEntry => addStylesheetEntry(importedEntry, entriesByRules, orderedEntries, visitedEntries));
1294
+ orderedEntries.push(entry);
1295
+ }
1296
+ }
1297
+
1298
+ function getImportedStylesheetEntries(entry, entriesByRules) {
1299
+ const importedEntries = [];
1300
+ const stylesheet = entry.stylesheetInfo.stylesheet;
1301
+ if (stylesheet && stylesheet.children) {
1302
+ stylesheet.children.forEach(ruleData => {
1303
+ if (ruleData.type == "Atrule" && ruleData.name == "import" && ruleData.prelude && ruleData.prelude.children && ruleData.prelude.children.head) {
1304
+ const importedEntry = entriesByRules.get(ruleData.prelude.children.head.data.importedChildren);
1305
+ if (importedEntry) {
1306
+ importedEntries.push(importedEntry);
1307
+ }
1308
+ }
1309
+ });
1310
+ }
1311
+ return importedEntries;
1312
+ }
1313
+
1249
1314
  function getContentSize(content) {
1250
1315
  return new Blob([content]).size;
1251
1316
  }
package/core/index.js CHANGED
@@ -1343,10 +1343,11 @@ class Processor {
1343
1343
  if (element.media) {
1344
1344
  mediaText = element.media.toLowerCase();
1345
1345
  }
1346
- const scoped = Boolean(element.closest("[" + SHADOWROOT_ATTRIBUTE_NAME + "]"));
1346
+ const scope = element.closest("[" + SHADOWROOT_ATTRIBUTE_NAME + "]");
1347
1347
  const stylesheetInfo = {
1348
1348
  mediaText,
1349
- scoped
1349
+ scoped: Boolean(scope),
1350
+ scope
1350
1351
  };
1351
1352
  await this.processorHelper.resolveStylesheets(element, stylesheetInfo, this.stylesheets, this.baseURI, options, this.workStyleElement, this.resources);
1352
1353
  }));
@@ -24,6 +24,8 @@
24
24
  import {
25
25
  normalizeFontFamily,
26
26
  getFontWeight,
27
+ getFontStretch,
28
+ getStylesheetsInCascadeOrder,
27
29
  getDataURI
28
30
  } from "./../helper.js";
29
31
  import { serialize as serializeSrcset } from "./../../vendor/html-srcset-parser.js";
@@ -51,17 +53,6 @@ const REGEXP_FONT_FORMAT = /\.([^.?#]+)((\?|#).*?)?$/;
51
53
  const REGEXP_FONT_FORMAT_VALUE = /format\((.*?)\)\s*,?$/;
52
54
  const REGEXP_FONT_SRC = /(.*?)\s*,?$/;
53
55
  const MEDIA_ALL = "all";
54
- const FONT_STRETCHES = {
55
- "ultra-condensed": "50%",
56
- "extra-condensed": "62.5%",
57
- "condensed": "75%",
58
- "semi-condensed": "87.5%",
59
- "normal": "100%",
60
- "semi-expanded": "112.5%",
61
- "expanded": "125%",
62
- "extra-expanded": "150%",
63
- "ultra-expanded": "200%"
64
- };
65
56
  const Blob = globalThis.Blob;
66
57
  const Image = globalThis.Image;
67
58
  const OffscreenCanvas = globalThis.OffscreenCanvas;
@@ -423,9 +414,12 @@ class ProcessorHelperCommon {
423
414
  async removeAlternativeFonts(doc, stylesheets, fonts, fontTests) {
424
415
  const fontsDetails = this.createFontsDetailsInfo();
425
416
  const stats = { rules: { processed: 0, discarded: 0 }, fonts: { processed: 0, discarded: 0 } };
426
- let sheetIndex = 0;
427
- stylesheets.forEach(stylesheetInfo => {
417
+ const ruleScopes = new Map();
418
+ getStylesheetsInCascadeOrder(stylesheets).forEach(({ stylesheetInfo, index: sheetIndex }) => {
428
419
  if (stylesheetInfo.stylesheet) {
420
+ if (stylesheetInfo.scope) {
421
+ cssTree.walk(stylesheetInfo.stylesheet, { visit: "Atrule", enter: node => ruleScopes.set(node, stylesheetInfo.scope) });
422
+ }
429
423
  const cssRules = stylesheetInfo.stylesheet.children;
430
424
  if (cssRules) {
431
425
  stats.rules.processed += cssRules.size;
@@ -439,10 +433,9 @@ class ProcessorHelperCommon {
439
433
  }
440
434
  }
441
435
  }
442
- sheetIndex++;
443
436
  });
444
437
  processFontDetails(fontsDetails, fonts);
445
- this.markRepeatedFontFaceRules(fontsDetails);
438
+ this.markRepeatedFontFaceRules(fontsDetails, ruleScopes, new Map());
446
439
  await Promise.all([...stylesheets].map(async ([, stylesheetInfo], sheetIndex) => {
447
440
  if (stylesheetInfo.stylesheet) {
448
441
  const cssRules = stylesheetInfo.stylesheet.children;
@@ -460,19 +453,23 @@ class ProcessorHelperCommon {
460
453
  return stats;
461
454
  }
462
455
 
463
- markRepeatedFontFaceRules(fontsDetails) {
456
+ markRepeatedFontFaceRules(fontsDetails, ruleScopes, scopeIds) {
464
457
  const lastRules = new Map();
465
458
  fontsDetails.fonts.forEach((fontInfo, ruleData) => {
466
- const ruleKey = this.getFontKey(ruleData) + " " + this.getPropertyValue(ruleData, "src");
459
+ const scope = ruleScopes.get(ruleData);
460
+ if (scope && !scopeIds.has(scope)) {
461
+ scopeIds.set(scope, scopeIds.size + 1);
462
+ }
463
+ const ruleKey = (scope ? scopeIds.get(scope) : 0) + " " + this.getFontKey(ruleData) + " " + this.getPropertyValue(ruleData, "src");
467
464
  const previousRuleData = lastRules.get(ruleKey);
468
465
  if (previousRuleData) {
469
466
  fontsDetails.repeatedFonts.add(previousRuleData);
470
467
  }
471
468
  lastRules.set(ruleKey, ruleData);
472
469
  });
473
- fontsDetails.medias.forEach(mediaFontsDetails => this.markRepeatedFontFaceRules(mediaFontsDetails));
474
- fontsDetails.supports.forEach(supportsFontsDetails => this.markRepeatedFontFaceRules(supportsFontsDetails));
475
- fontsDetails.layers.forEach(layerFontsDetails => this.markRepeatedFontFaceRules(layerFontsDetails));
470
+ fontsDetails.medias.forEach(mediaFontsDetails => this.markRepeatedFontFaceRules(mediaFontsDetails, ruleScopes, scopeIds));
471
+ fontsDetails.supports.forEach(supportsFontsDetails => this.markRepeatedFontFaceRules(supportsFontsDetails, ruleScopes, scopeIds));
472
+ fontsDetails.layers.forEach(layerFontsDetails => this.markRepeatedFontFaceRules(layerFontsDetails, ruleScopes, scopeIds));
476
473
  }
477
474
 
478
475
  async processFontFaceRules(cssRules, sheetIndex, fontsDetails, fonts, fontTests, stats) {
@@ -739,10 +736,6 @@ function getURL(urlFunction) {
739
736
  return urlMatch && urlMatch[1];
740
737
  }
741
738
 
742
- function getFontStretch(stretch) {
743
- return FONT_STRETCHES[stretch] || stretch;
744
- }
745
-
746
739
  async function resizeImage(doc, dataURI, { imageReductionFactor, imageQuality }, element) {
747
740
  if (dataURI) {
748
741
  const contentType = dataURI.substring(5, dataURI.indexOf(";"));
@@ -582,9 +582,15 @@ function getProcessorHelperClass(utilInstance) {
582
582
  groupDuplicateFonts(stylesheets, fonts, options) {
583
583
  if (options.usedFonts && options.usedFonts.length) {
584
584
  const fontFaces = [];
585
+ const scopeIds = new Map();
585
586
  stylesheets.forEach(stylesheetInfo => {
586
587
  if (stylesheetInfo.stylesheet && stylesheetInfo.stylesheet.children) {
587
- getFontFaces(stylesheetInfo.stylesheet.children, "", fontFaces);
588
+ const scope = stylesheetInfo.scope || null;
589
+ if (!scopeIds.has(scope)) {
590
+ scopeIds.set(scope, scopeIds.size);
591
+ }
592
+ const mediaScope = stylesheetInfo.mediaText ? "|media " + stylesheetInfo.mediaText : "";
593
+ getFontFaces(stylesheetInfo.stylesheet.children, scopeIds.get(scope) + mediaScope, fontFaces);
588
594
  }
589
595
  });
590
596
  const families = new Map();
@@ -216,7 +216,7 @@ function getProcessorHelperClass(utilInstance) {
216
216
 
217
217
  async resolveImportURLs(stylesheetInfo, baseURI, options, workStylesheet, resources, stylesheets, importedStyleSheets = new Set(), stylesheetReferrer) {
218
218
  const stylesheet = stylesheetInfo.stylesheet;
219
- const scoped = stylesheetInfo.scoped;
219
+ const { scoped, scope } = stylesheetInfo;
220
220
  this.resolveStylesheetURLs(stylesheet, baseURI, workStylesheet, stylesheetReferrer, options.stylesheetReferrers);
221
221
  const imports = getImportFunctions(stylesheet);
222
222
  await Promise.all(imports.map(async node => {
@@ -247,6 +247,7 @@ function getProcessorHelperClass(utilInstance) {
247
247
  }
248
248
  const stylesheetInfo = {
249
249
  scoped,
250
+ scope,
250
251
  mediaText,
251
252
  layerName,
252
253
  supportsCondition