[api-minor] Use a local font or fallback on an embedded one (if it exists) for non-embedded fonts (bug 1766039)
- Replace FoxitSans with LiberationSans: LiberationSans is already there (for XFA) and we can use it as a good replacement of FoxitSans. - For now we just try to substitue standard fonts, the strategy is the following: * we try to find a font locally from a hardcoded list; * if it fails then we use Liberation as fallback (only for Helvetica for the moment); * else we just fallback on the system serif/sansserif/monospace font.
This commit is contained in:
parent
a24e11a91c
commit
53134c0c0b
BIN
external/standard_fonts/FoxitSans.pfb
vendored
BIN
external/standard_fonts/FoxitSans.pfb
vendored
Binary file not shown.
BIN
external/standard_fonts/FoxitSansBold.pfb
vendored
BIN
external/standard_fonts/FoxitSansBold.pfb
vendored
Binary file not shown.
BIN
external/standard_fonts/FoxitSansBoldItalic.pfb
vendored
BIN
external/standard_fonts/FoxitSansBoldItalic.pfb
vendored
Binary file not shown.
BIN
external/standard_fonts/FoxitSansItalic.pfb
vendored
BIN
external/standard_fonts/FoxitSansItalic.pfb
vendored
Binary file not shown.
@ -423,6 +423,31 @@ function encodeToXmlString(str) {
|
|||||||
return buffer.join("");
|
return buffer.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateFontName(fontFamily, mustWarn = false) {
|
||||||
|
// See https://developer.mozilla.org/en-US/docs/Web/CSS/string.
|
||||||
|
const m = /^("|').*("|')$/.exec(fontFamily);
|
||||||
|
if (m && m[1] === m[2]) {
|
||||||
|
const re = new RegExp(`[^\\\\]${m[1]}`);
|
||||||
|
if (re.test(fontFamily.slice(1, -1))) {
|
||||||
|
if (mustWarn) {
|
||||||
|
warn(`FontFamily contains unescaped ${m[1]}: ${fontFamily}.`);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// See https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident.
|
||||||
|
for (const ident of fontFamily.split(/[ \t]+/)) {
|
||||||
|
if (/^(\d|(-(\d|-)))/.test(ident) || !/^[\w-\\]+$/.test(ident)) {
|
||||||
|
if (mustWarn) {
|
||||||
|
warn(`FontFamily contains invalid <custom-ident>: ${fontFamily}.`);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function validateCSSFont(cssFontInfo) {
|
function validateCSSFont(cssFontInfo) {
|
||||||
// See https://developer.mozilla.org/en-US/docs/Web/CSS/font-style.
|
// See https://developer.mozilla.org/en-US/docs/Web/CSS/font-style.
|
||||||
const DEFAULT_CSS_FONT_OBLIQUE = "14";
|
const DEFAULT_CSS_FONT_OBLIQUE = "14";
|
||||||
@ -447,25 +472,9 @@ function validateCSSFont(cssFontInfo) {
|
|||||||
|
|
||||||
const { fontFamily, fontWeight, italicAngle } = cssFontInfo;
|
const { fontFamily, fontWeight, italicAngle } = cssFontInfo;
|
||||||
|
|
||||||
// See https://developer.mozilla.org/en-US/docs/Web/CSS/string.
|
if (!validateFontName(fontFamily, true)) {
|
||||||
const m = /^("|').*("|')$/.exec(fontFamily);
|
|
||||||
if (m && m[1] === m[2]) {
|
|
||||||
const re = new RegExp(`[^\\\\]${m[1]}`);
|
|
||||||
if (re.test(fontFamily.slice(1, -1))) {
|
|
||||||
warn(`XFA - FontFamily contains unescaped ${m[1]}: ${fontFamily}.`);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// See https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident.
|
|
||||||
for (const ident of fontFamily.split(/[ \t]+/)) {
|
|
||||||
if (/^(\d|(-(\d|-)))/.test(ident) || !/^[\w-\\]+$/.test(ident)) {
|
|
||||||
warn(
|
|
||||||
`XFA - FontFamily contains invalid <custom-ident>: ${fontFamily}.`
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const weight = fontWeight ? fontWeight.toString() : "";
|
const weight = fontWeight ? fontWeight.toString() : "";
|
||||||
cssFontInfo.fontWeight = CSS_FONT_WEIGHT_VALUES.has(weight)
|
cssFontInfo.fontWeight = CSS_FONT_WEIGHT_VALUES.has(weight)
|
||||||
@ -617,6 +626,7 @@ export {
|
|||||||
stringToUTF16String,
|
stringToUTF16String,
|
||||||
toRomanNumerals,
|
toRomanNumerals,
|
||||||
validateCSSFont,
|
validateCSSFont,
|
||||||
|
validateFontName,
|
||||||
XRefEntryException,
|
XRefEntryException,
|
||||||
XRefParseException,
|
XRefParseException,
|
||||||
};
|
};
|
||||||
|
@ -68,6 +68,7 @@ import { bidi } from "./bidi.js";
|
|||||||
import { ColorSpace } from "./colorspace.js";
|
import { ColorSpace } from "./colorspace.js";
|
||||||
import { DecodeStream } from "./decode_stream.js";
|
import { DecodeStream } from "./decode_stream.js";
|
||||||
import { FontFlags } from "./fonts_utils.js";
|
import { FontFlags } from "./fonts_utils.js";
|
||||||
|
import { getFontSubstitution } from "./font_substitutions.js";
|
||||||
import { getGlyphsUnicode } from "./glyphlist.js";
|
import { getGlyphsUnicode } from "./glyphlist.js";
|
||||||
import { getLookupTableFactory } from "./core_utils.js";
|
import { getLookupTableFactory } from "./core_utils.js";
|
||||||
import { getMetrics } from "./metrics.js";
|
import { getMetrics } from "./metrics.js";
|
||||||
@ -4174,6 +4175,7 @@ class PartialEvaluator {
|
|||||||
type,
|
type,
|
||||||
name: baseFontName,
|
name: baseFontName,
|
||||||
loadedName: baseDict.loadedName,
|
loadedName: baseDict.loadedName,
|
||||||
|
systemFontInfo: null,
|
||||||
widths: metrics.widths,
|
widths: metrics.widths,
|
||||||
defaultWidth: metrics.defaultWidth,
|
defaultWidth: metrics.defaultWidth,
|
||||||
isSimulatedFlags: true,
|
isSimulatedFlags: true,
|
||||||
@ -4193,6 +4195,14 @@ class PartialEvaluator {
|
|||||||
if (standardFontName) {
|
if (standardFontName) {
|
||||||
file = await this.fetchStandardFontData(standardFontName);
|
file = await this.fetchStandardFontData(standardFontName);
|
||||||
properties.isInternalFont = !!file;
|
properties.isInternalFont = !!file;
|
||||||
|
if (!properties.isInternalFont && this.options.useSystemFonts) {
|
||||||
|
properties.systemFontInfo = getFontSubstitution(
|
||||||
|
this.idFactory,
|
||||||
|
this.options.standardFontDataUrl,
|
||||||
|
baseFontName,
|
||||||
|
standardFontName
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this.extractDataStructures(dict, dict, properties).then(
|
return this.extractDataStructures(dict, dict, properties).then(
|
||||||
newProperties => {
|
newProperties => {
|
||||||
@ -4264,6 +4274,7 @@ class PartialEvaluator {
|
|||||||
}
|
}
|
||||||
let isInternalFont = false;
|
let isInternalFont = false;
|
||||||
let glyphScaleFactors = null;
|
let glyphScaleFactors = null;
|
||||||
|
let systemFontInfo = null;
|
||||||
if (fontFile) {
|
if (fontFile) {
|
||||||
if (fontFile.dict) {
|
if (fontFile.dict) {
|
||||||
const subtypeEntry = fontFile.dict.get("Subtype");
|
const subtypeEntry = fontFile.dict.get("Subtype");
|
||||||
@ -4296,6 +4307,14 @@ class PartialEvaluator {
|
|||||||
if (standardFontName) {
|
if (standardFontName) {
|
||||||
fontFile = await this.fetchStandardFontData(standardFontName);
|
fontFile = await this.fetchStandardFontData(standardFontName);
|
||||||
isInternalFont = !!fontFile;
|
isInternalFont = !!fontFile;
|
||||||
|
if (!isInternalFont && this.options.useSystemFonts) {
|
||||||
|
systemFontInfo = getFontSubstitution(
|
||||||
|
this.idFactory,
|
||||||
|
this.options.standardFontDataUrl,
|
||||||
|
fontName.name,
|
||||||
|
standardFontName
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4325,6 +4344,7 @@ class PartialEvaluator {
|
|||||||
isType3Font,
|
isType3Font,
|
||||||
cssFontInfo,
|
cssFontInfo,
|
||||||
scaleFactors: glyphScaleFactors,
|
scaleFactors: glyphScaleFactors,
|
||||||
|
systemFontInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (composite) {
|
if (composite) {
|
||||||
|
478
src/core/font_substitutions.js
Normal file
478
src/core/font_substitutions.js
Normal file
@ -0,0 +1,478 @@
|
|||||||
|
/* Copyright 2023 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { normalizeFontName } from "./fonts_utils.js";
|
||||||
|
import { validateFontName } from "./core_utils.js";
|
||||||
|
|
||||||
|
const NORMAL = {
|
||||||
|
style: "normal",
|
||||||
|
weight: "normal",
|
||||||
|
};
|
||||||
|
const BOLD = {
|
||||||
|
style: "normal",
|
||||||
|
weight: "bold",
|
||||||
|
};
|
||||||
|
const ITALIC = {
|
||||||
|
style: "italic",
|
||||||
|
weight: "normal",
|
||||||
|
};
|
||||||
|
const BOLDITALIC = {
|
||||||
|
style: "italic",
|
||||||
|
weight: "bold",
|
||||||
|
};
|
||||||
|
|
||||||
|
const substitutionMap = new Map([
|
||||||
|
[
|
||||||
|
"Times-Roman",
|
||||||
|
{
|
||||||
|
local: [
|
||||||
|
"Times New Roman",
|
||||||
|
"Times-Roman",
|
||||||
|
"Times",
|
||||||
|
"Liberation Serif",
|
||||||
|
"Nimbus Roman",
|
||||||
|
"Nimbus Roman L",
|
||||||
|
"Tinos",
|
||||||
|
"Thorndale",
|
||||||
|
"TeX Gyre Termes",
|
||||||
|
"FreeSerif",
|
||||||
|
"DejaVu Serif",
|
||||||
|
"Bitstream Vera Serif",
|
||||||
|
"Ubuntu",
|
||||||
|
],
|
||||||
|
style: NORMAL,
|
||||||
|
ultimate: "serif",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Times-Bold",
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
alias: "Times-Roman",
|
||||||
|
append: "Bold",
|
||||||
|
},
|
||||||
|
style: BOLD,
|
||||||
|
ultimate: "serif",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Times-Italic",
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
alias: "Times-Roman",
|
||||||
|
append: "Italic",
|
||||||
|
},
|
||||||
|
style: ITALIC,
|
||||||
|
ultimate: "serif",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Times-BoldItalic",
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
alias: "Times-Roman",
|
||||||
|
append: "Bold Italic",
|
||||||
|
},
|
||||||
|
style: BOLDITALIC,
|
||||||
|
ultimate: "serif",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Helvetica",
|
||||||
|
{
|
||||||
|
local: [
|
||||||
|
"Helvetica",
|
||||||
|
"Helvetica Neue",
|
||||||
|
"Arial",
|
||||||
|
"Arial Nova",
|
||||||
|
"Liberation Sans",
|
||||||
|
"Arimo",
|
||||||
|
"Nimbus Sans",
|
||||||
|
"Nimbus Sans L",
|
||||||
|
"A030",
|
||||||
|
"TeX Gyre Heros",
|
||||||
|
"FreeSans",
|
||||||
|
"DejaVu Sans",
|
||||||
|
"Albany",
|
||||||
|
"Bitstream Vera Sans",
|
||||||
|
"Arial Unicode MS",
|
||||||
|
"Microsoft Sans Serif",
|
||||||
|
"Apple Symbols",
|
||||||
|
"Cantarell",
|
||||||
|
],
|
||||||
|
path: "LiberationSans-Regular.ttf",
|
||||||
|
style: NORMAL,
|
||||||
|
ultimate: "sans-serif",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Helvetica-Bold",
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
alias: "Helvetica",
|
||||||
|
append: "Bold",
|
||||||
|
},
|
||||||
|
path: "LiberationSans-Bold.ttf",
|
||||||
|
style: BOLD,
|
||||||
|
ultimate: "sans-serif",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Helvetica-Oblique",
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
alias: "Helvetica",
|
||||||
|
append: "Italic",
|
||||||
|
},
|
||||||
|
path: "LiberationSans-Italic.ttf",
|
||||||
|
style: ITALIC,
|
||||||
|
ultimate: "sans-serif",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Helvetica-BoldOblique",
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
alias: "Helvetica",
|
||||||
|
append: "Bold Italic",
|
||||||
|
},
|
||||||
|
path: "LiberationSans-BoldItalic.ttf",
|
||||||
|
style: BOLDITALIC,
|
||||||
|
ultimate: "sans-serif",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Courier",
|
||||||
|
{
|
||||||
|
local: [
|
||||||
|
"Courier",
|
||||||
|
"Courier New",
|
||||||
|
"Liberation Mono",
|
||||||
|
"Nimbus Mono",
|
||||||
|
"Nimbus Mono L",
|
||||||
|
"Cousine",
|
||||||
|
"Cumberland",
|
||||||
|
"TeX Gyre Cursor",
|
||||||
|
"FreeMono",
|
||||||
|
],
|
||||||
|
style: NORMAL,
|
||||||
|
ultimate: "monospace",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Courier-Bold",
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
alias: "Courier",
|
||||||
|
append: "Bold",
|
||||||
|
},
|
||||||
|
style: BOLD,
|
||||||
|
ultimate: "monospace",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Courier-Oblique",
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
alias: "Courier",
|
||||||
|
append: "Italic",
|
||||||
|
},
|
||||||
|
style: ITALIC,
|
||||||
|
ultimate: "monospace",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Courier-BoldOblique",
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
alias: "Courier",
|
||||||
|
append: "Bold Italic",
|
||||||
|
},
|
||||||
|
style: BOLDITALIC,
|
||||||
|
ultimate: "monospace",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ArialBlack",
|
||||||
|
{
|
||||||
|
prepend: ["Arial Black"],
|
||||||
|
style: {
|
||||||
|
style: "normal",
|
||||||
|
weight: "900",
|
||||||
|
},
|
||||||
|
fallback: "Helvetica-Bold",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ArialBlack-Bold",
|
||||||
|
{
|
||||||
|
alias: "ArialBlack",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ArialBlack-Italic",
|
||||||
|
{
|
||||||
|
prepend: ["Arial Black Italic"],
|
||||||
|
local: {
|
||||||
|
alias: "ArialBlack",
|
||||||
|
append: "Italic",
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
style: "italic",
|
||||||
|
weight: "900",
|
||||||
|
},
|
||||||
|
fallback: "Helvetica-BoldOblique",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ArialBlack-BoldItalic",
|
||||||
|
{
|
||||||
|
alias: "ArialBlack-Italic",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ArialNarrow",
|
||||||
|
{
|
||||||
|
prepend: [
|
||||||
|
"Arial Narrow",
|
||||||
|
"Liberation Sans Narrow",
|
||||||
|
"Helvetica Condensed",
|
||||||
|
"Nimbus Sans Narrow",
|
||||||
|
"TeX Gyre Heros Cn",
|
||||||
|
],
|
||||||
|
style: NORMAL,
|
||||||
|
fallback: "Helvetica",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ArialNarrow-Bold",
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
alias: "ArialNarrow",
|
||||||
|
append: "Bold",
|
||||||
|
},
|
||||||
|
style: BOLD,
|
||||||
|
fallback: "Helvetica-Bold",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ArialNarrow-Italic",
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
alias: "ArialNarrow",
|
||||||
|
append: "Italic",
|
||||||
|
},
|
||||||
|
style: ITALIC,
|
||||||
|
fallback: "Helvetica-Oblique",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"ArialNarrow-BoldItalic",
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
alias: "ArialNarrow",
|
||||||
|
append: "Bold Italic",
|
||||||
|
},
|
||||||
|
style: BOLDITALIC,
|
||||||
|
fallback: "Helvetica-BoldOblique",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Calibri",
|
||||||
|
{
|
||||||
|
prepend: ["Calibri", "Carlito"],
|
||||||
|
style: NORMAL,
|
||||||
|
fallback: "Helvetica",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Calibri-Bold",
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
alias: "Calibri",
|
||||||
|
append: "Bold",
|
||||||
|
},
|
||||||
|
style: BOLD,
|
||||||
|
fallback: "Helvetica-Bold",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Calibri-Italic",
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
alias: "Calibri",
|
||||||
|
append: "Italic",
|
||||||
|
},
|
||||||
|
style: ITALIC,
|
||||||
|
fallback: "Helvetica-Oblique",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Calibri-BoldItalic",
|
||||||
|
{
|
||||||
|
local: {
|
||||||
|
alias: "Calibri",
|
||||||
|
append: "Bold Italic",
|
||||||
|
},
|
||||||
|
style: BOLDITALIC,
|
||||||
|
fallback: "Helvetica-BoldOblique",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const fontAliases = new Map([["Arial-Black", "ArialBlack"]]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the src path to use to load a font (see FontFace).
|
||||||
|
* @param {Array<String>} prepend A list of font names to search first.
|
||||||
|
* @param {Array<String>|Object} local A list of font names to search. If an
|
||||||
|
* Object is passed, then local.alias is the name of an other substition font
|
||||||
|
* and local.append is a String to append to the list of fonts in the alias.
|
||||||
|
* For example if local.alias is "Foo" and local.append is "Bold" then the
|
||||||
|
* list of fonts will be "FooSubst1 Bold", "FooSubst2 Bold", etc.
|
||||||
|
* @returns an String with the local fonts.
|
||||||
|
*/
|
||||||
|
function makeLocal(prepend, local) {
|
||||||
|
let append = "";
|
||||||
|
if (!Array.isArray(local)) {
|
||||||
|
// We are getting our list of fonts in the alias and we'll append Bold,
|
||||||
|
// Italic or both.
|
||||||
|
append = ` ${local.append}`;
|
||||||
|
local = substitutionMap.get(local.alias).local;
|
||||||
|
}
|
||||||
|
let prependedPaths = "";
|
||||||
|
if (prepend) {
|
||||||
|
prependedPaths = prepend.map(name => `local(${name})`).join(",") + ",";
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
prependedPaths + local.map(name => `local(${name}${append})`).join(",")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a font substitution for a given font.
|
||||||
|
* The general idea is to have enough information to create a CSS rule like
|
||||||
|
* this:
|
||||||
|
* @font-face {
|
||||||
|
* font-family: 'Times';
|
||||||
|
* src: local('Times New Roman'), local('Subst1'), local('Subst2'),
|
||||||
|
* url(.../TimesNewRoman.ttf)
|
||||||
|
* font-weight: normal;
|
||||||
|
* font-style: normal;
|
||||||
|
* }
|
||||||
|
* or use the FontFace API.
|
||||||
|
*
|
||||||
|
* @param {Object} idFactory The ids factory.
|
||||||
|
* @param {String} localFontPath Path to the fonts directory.
|
||||||
|
* @param {String} baseFontName The font name to be substituted.
|
||||||
|
* @param {String} standardFontName The standard font name to use if the base
|
||||||
|
* font is not available.
|
||||||
|
* @returns an Object with the CSS, the loaded name, the src and the style.
|
||||||
|
*/
|
||||||
|
function getFontSubstitution(
|
||||||
|
idFactory,
|
||||||
|
localFontPath,
|
||||||
|
baseFontName,
|
||||||
|
standardFontName
|
||||||
|
) {
|
||||||
|
let mustAddBaseFont = false;
|
||||||
|
|
||||||
|
// It's possible to have a font name with spaces, commas or dashes, hence we
|
||||||
|
// just replace them by a dash.
|
||||||
|
baseFontName = normalizeFontName(baseFontName);
|
||||||
|
|
||||||
|
// First, check if we've a substitution for the base font.
|
||||||
|
let substitution = substitutionMap.get(baseFontName);
|
||||||
|
if (!substitution) {
|
||||||
|
// Check if we've an alias for the base font, Arial-Black is the same as
|
||||||
|
// ArialBlack
|
||||||
|
for (const [alias, subst] of fontAliases) {
|
||||||
|
if (baseFontName.startsWith(alias)) {
|
||||||
|
baseFontName = `${subst}${baseFontName.substring(alias.length)}`;
|
||||||
|
substitution = substitutionMap.get(baseFontName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!substitution) {
|
||||||
|
// If not, check if we've a substitution for the standard font.
|
||||||
|
substitution = substitutionMap.get(standardFontName);
|
||||||
|
mustAddBaseFont = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadedName = `${idFactory.getDocId()}_sf_${idFactory.createFontId()}`;
|
||||||
|
if (!substitution) {
|
||||||
|
if (!validateFontName(baseFontName)) {
|
||||||
|
// If the baseFontName is not valid we don't want to use it.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Maybe we'll be lucky and the OS will have the font.
|
||||||
|
const bold = /bold/gi.test(baseFontName);
|
||||||
|
const italic = /oblique|italic/gi.test(baseFontName);
|
||||||
|
const style =
|
||||||
|
(bold && italic && BOLDITALIC) ||
|
||||||
|
(bold && BOLD) ||
|
||||||
|
(italic && ITALIC) ||
|
||||||
|
NORMAL;
|
||||||
|
return {
|
||||||
|
css: `${loadedName},sans-serif`,
|
||||||
|
loadedName,
|
||||||
|
src: `local(${baseFontName})`,
|
||||||
|
style,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
while (substitution.alias) {
|
||||||
|
// If we've an alias, use the substitution for the alias.
|
||||||
|
// For example, ArialBlack-Bold is an alias for ArialBlack because the bold
|
||||||
|
// version of Arial Black is not available.
|
||||||
|
substitution = substitutionMap.get(substitution.alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { fallback, style } = substitution;
|
||||||
|
|
||||||
|
// Prepend the fonts to test before the fallback font.
|
||||||
|
let prepend = substitution.prepend;
|
||||||
|
|
||||||
|
if (fallback) {
|
||||||
|
// We've a fallback font: this one is a standard font we want to use in case
|
||||||
|
// nothing has been found from the prepend list.
|
||||||
|
prepend ||= substitutionMap.get(substitution.local.alias).prepend;
|
||||||
|
substitution = substitutionMap.get(fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { local, path, ultimate } = substitution;
|
||||||
|
let src = makeLocal(prepend, local);
|
||||||
|
if (path && localFontPath !== null) {
|
||||||
|
// PDF.js embeds some fonts we can use.
|
||||||
|
src += `,url(${localFontPath}${path})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe the OS will have the exact font we want so just prepend it to the
|
||||||
|
// list.
|
||||||
|
if (mustAddBaseFont && validateFontName(baseFontName)) {
|
||||||
|
src = `local(${baseFontName}),${src}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
css: `${loadedName},${ultimate}`,
|
||||||
|
loadedName,
|
||||||
|
src,
|
||||||
|
style,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getFontSubstitution };
|
@ -98,6 +98,7 @@ const EXPORT_DATA_PROPERTIES = [
|
|||||||
"name",
|
"name",
|
||||||
"remeasure",
|
"remeasure",
|
||||||
"subtype",
|
"subtype",
|
||||||
|
"systemFontInfo",
|
||||||
"type",
|
"type",
|
||||||
"vertical",
|
"vertical",
|
||||||
];
|
];
|
||||||
@ -998,6 +999,7 @@ class Font {
|
|||||||
this.fallbackName = "sans-serif";
|
this.fallbackName = "sans-serif";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.systemFontInfo = properties.systemFontInfo;
|
||||||
this.differences = properties.differences;
|
this.differences = properties.differences;
|
||||||
this.widths = properties.widths;
|
this.widths = properties.widths;
|
||||||
this.defaultWidth = properties.defaultWidth;
|
this.defaultWidth = properties.defaultWidth;
|
||||||
|
@ -100,10 +100,10 @@ const getFontNameToFileMap = getLookupTableFactory(function (t) {
|
|||||||
t["Courier-Bold"] = "FoxitFixedBold.pfb";
|
t["Courier-Bold"] = "FoxitFixedBold.pfb";
|
||||||
t["Courier-BoldOblique"] = "FoxitFixedBoldItalic.pfb";
|
t["Courier-BoldOblique"] = "FoxitFixedBoldItalic.pfb";
|
||||||
t["Courier-Oblique"] = "FoxitFixedItalic.pfb";
|
t["Courier-Oblique"] = "FoxitFixedItalic.pfb";
|
||||||
t.Helvetica = "FoxitSans.pfb";
|
t.Helvetica = "LiberationSans-Regular.ttf";
|
||||||
t["Helvetica-Bold"] = "FoxitSansBold.pfb";
|
t["Helvetica-Bold"] = "LiberationSans-Bold.ttf";
|
||||||
t["Helvetica-BoldOblique"] = "FoxitSansBoldItalic.pfb";
|
t["Helvetica-BoldOblique"] = "LiberationSans-BoldItalic.ttf";
|
||||||
t["Helvetica-Oblique"] = "FoxitSansItalic.pfb";
|
t["Helvetica-Oblique"] = "LiberationSans-Italic.ttf";
|
||||||
t["Times-Roman"] = "FoxitSerif.pfb";
|
t["Times-Roman"] = "FoxitSerif.pfb";
|
||||||
t["Times-Bold"] = "FoxitSerifBold.pfb";
|
t["Times-Bold"] = "FoxitSerifBold.pfb";
|
||||||
t["Times-BoldItalic"] = "FoxitSerifBoldItalic.pfb";
|
t["Times-BoldItalic"] = "FoxitSerifBoldItalic.pfb";
|
||||||
|
@ -1950,6 +1950,8 @@ class CanvasGraphics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const name = fontObj.loadedName || "sans-serif";
|
const name = fontObj.loadedName || "sans-serif";
|
||||||
|
const typeface =
|
||||||
|
fontObj.systemFontInfo?.css || `"${name}", ${fontObj.fallbackName}`;
|
||||||
|
|
||||||
let bold = "normal";
|
let bold = "normal";
|
||||||
if (fontObj.black) {
|
if (fontObj.black) {
|
||||||
@ -1958,7 +1960,6 @@ class CanvasGraphics {
|
|||||||
bold = "bold";
|
bold = "bold";
|
||||||
}
|
}
|
||||||
const italic = fontObj.italic ? "italic" : "normal";
|
const italic = fontObj.italic ? "italic" : "normal";
|
||||||
const typeface = `"${name}", ${fontObj.fallbackName}`;
|
|
||||||
|
|
||||||
// Some font backends cannot handle fonts below certain size.
|
// Some font backends cannot handle fonts below certain size.
|
||||||
// Keeping the font at minimal size and using the fontSizeScale to change
|
// Keeping the font at minimal size and using the fontSizeScale to change
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
FeatureTest,
|
FeatureTest,
|
||||||
shadow,
|
shadow,
|
||||||
string32,
|
string32,
|
||||||
|
unreachable,
|
||||||
warn,
|
warn,
|
||||||
} from "../shared/util.js";
|
} from "../shared/util.js";
|
||||||
import { isNodeJS } from "../shared/is_node.js";
|
import { isNodeJS } from "../shared/is_node.js";
|
||||||
@ -30,7 +31,7 @@ class FontLoader {
|
|||||||
}) {
|
}) {
|
||||||
this._document = ownerDocument;
|
this._document = ownerDocument;
|
||||||
|
|
||||||
this.nativeFontFaces = [];
|
this.nativeFontFaces = new Set();
|
||||||
this.styleElement =
|
this.styleElement =
|
||||||
typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")
|
typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")
|
||||||
? styleElement
|
? styleElement
|
||||||
@ -43,10 +44,15 @@ class FontLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addNativeFontFace(nativeFontFace) {
|
addNativeFontFace(nativeFontFace) {
|
||||||
this.nativeFontFaces.push(nativeFontFace);
|
this.nativeFontFaces.add(nativeFontFace);
|
||||||
this._document.fonts.add(nativeFontFace);
|
this._document.fonts.add(nativeFontFace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeNativeFontFace(nativeFontFace) {
|
||||||
|
this.nativeFontFaces.delete(nativeFontFace);
|
||||||
|
this._document.fonts.delete(nativeFontFace);
|
||||||
|
}
|
||||||
|
|
||||||
insertRule(rule) {
|
insertRule(rule) {
|
||||||
if (!this.styleElement) {
|
if (!this.styleElement) {
|
||||||
this.styleElement = this._document.createElement("style");
|
this.styleElement = this._document.createElement("style");
|
||||||
@ -62,7 +68,7 @@ class FontLoader {
|
|||||||
for (const nativeFontFace of this.nativeFontFaces) {
|
for (const nativeFontFace of this.nativeFontFaces) {
|
||||||
this._document.fonts.delete(nativeFontFace);
|
this._document.fonts.delete(nativeFontFace);
|
||||||
}
|
}
|
||||||
this.nativeFontFaces.length = 0;
|
this.nativeFontFaces.clear();
|
||||||
|
|
||||||
if (this.styleElement) {
|
if (this.styleElement) {
|
||||||
// Note: ChildNode.remove doesn't throw if the parentNode is undefined.
|
// Note: ChildNode.remove doesn't throw if the parentNode is undefined.
|
||||||
@ -71,13 +77,44 @@ class FontLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadSystemFont(info) {
|
||||||
|
assert(
|
||||||
|
!this.disableFontFace,
|
||||||
|
"loadSystemFont shouldn't be called when `disableFontFace` is set."
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.isFontLoadingAPISupported) {
|
||||||
|
const { loadedName, src, style } = info;
|
||||||
|
const fontFace = new FontFace(loadedName, src, style);
|
||||||
|
this.addNativeFontFace(fontFace);
|
||||||
|
try {
|
||||||
|
await fontFace.load();
|
||||||
|
} catch {
|
||||||
|
warn(
|
||||||
|
`Cannot load system font: ${loadedName} for style ${style.style} and weight ${style.weight}.`
|
||||||
|
);
|
||||||
|
this.removeNativeFontFace(fontFace);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable(
|
||||||
|
"Not implemented: loadSystemFont without the Font Loading API."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async bind(font) {
|
async bind(font) {
|
||||||
// Add the font to the DOM only once; skip if the font is already loaded.
|
// Add the font to the DOM only once; skip if the font is already loaded.
|
||||||
if (font.attached || font.missingFile) {
|
if (font.attached || (font.missingFile && !font.systemFontInfo)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
font.attached = true;
|
font.attached = true;
|
||||||
|
|
||||||
|
if (font.systemFontInfo) {
|
||||||
|
await this.loadSystemFont(font.systemFontInfo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isFontLoadingAPISupported) {
|
if (this.isFontLoadingAPISupported) {
|
||||||
const nativeFontFace = font.createNativeFontFace();
|
const nativeFontFace = font.createNativeFontFace();
|
||||||
if (nativeFontFace) {
|
if (nativeFontFace) {
|
||||||
|
@ -210,7 +210,12 @@ const defaultOptions = {
|
|||||||
cMapUrl: {
|
cMapUrl: {
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
value:
|
value:
|
||||||
typeof PDFJSDev === "undefined" ? "../external/bcmaps/" : "../web/cmaps/",
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
typeof PDFJSDev === "undefined"
|
||||||
|
? "../external/bcmaps/"
|
||||||
|
: PDFJSDev.test("MOZCENTRAL")
|
||||||
|
? "resource://pdf.js/web/cmaps/"
|
||||||
|
: "../web/cmaps/",
|
||||||
kind: OptionKind.API,
|
kind: OptionKind.API,
|
||||||
},
|
},
|
||||||
disableAutoFetch: {
|
disableAutoFetch: {
|
||||||
@ -271,8 +276,11 @@ const defaultOptions = {
|
|||||||
standardFontDataUrl: {
|
standardFontDataUrl: {
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
value:
|
value:
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
typeof PDFJSDev === "undefined"
|
typeof PDFJSDev === "undefined"
|
||||||
? "../external/standard_fonts/"
|
? "../external/standard_fonts/"
|
||||||
|
: PDFJSDev.test("MOZCENTRAL")
|
||||||
|
? "resource://pdf.js/web/standard_fonts/"
|
||||||
: "../web/standard_fonts/",
|
: "../web/standard_fonts/",
|
||||||
kind: OptionKind.API,
|
kind: OptionKind.API,
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user