fix fonts and give 4 options
This commit is contained in:
@@ -16,10 +16,10 @@ Temporary add-ons remain installed until Firefox restarts. For permanent local u
|
|||||||
|
|
||||||
1. Open a normal web page.
|
1. Open a normal web page.
|
||||||
2. Select the Glagolify toolbar icon.
|
2. Select the Glagolify toolbar icon.
|
||||||
3. Choose **Round** or **Angular** letter shapes.
|
3. Choose **Round**, **Angular Sans**, **Historical Angular**, or **Missal Angular** letter shapes.
|
||||||
4. Press **Enable Glagolitic**. Press **Restore Cyrillic** to undo it.
|
4. Press **Enable Glagolitic**. Press **Restore Cyrillic** to undo it.
|
||||||
|
|
||||||
The popup keeps one action button and a two-choice letter-style selector. The selected style is stored as a global extension preference and updates every enabled tab immediately. The background script separately owns one absolute enabled/disabled state per tab, broadcasts it to every frame, and supplies both state and style to frames created or navigated later. Tab state lasts across navigation and is discarded when the tab closes. Firefox internal pages (`about:`, browser UI), extension pages, and other protected pages cannot be modified; the popup reports that restriction.
|
The popup keeps one action button and a four-choice letter-style selector. The selected style is stored as a global extension preference and updates every enabled tab immediately. The background script separately owns one absolute enabled/disabled state per tab, broadcasts it to every frame, and supplies both state and style to frames created or navigated later. Tab state lasts across navigation and is discarded when the tab closes. Firefox internal pages (`about:`, browser UI), extension pages, and other protected pages cannot be modified; the popup reports that restriction.
|
||||||
|
|
||||||
Glagolify updates existing text and watches for text nodes that are inserted or changed later. It leaves `script`, `style`, `noscript`, `template`, `textarea`, `select`, `option`, effective `contenteditable` regions, and documents in `designMode` untouched. Visible form labels, validation messages, and button text are converted, while text controls and selectable options are not. Textual DOM hidden by HTML, CSS, or `aria-hidden` is converted proactively so it is already Glagolitic if revealed. The implementation only changes text-node data and a reversible inline font fallback; it does not replace or wrap page elements. Disconnected nodes are restored and released after each mutation batch, while nodes moved and reconnected in the same batch keep their state.
|
Glagolify updates existing text and watches for text nodes that are inserted or changed later. It leaves `script`, `style`, `noscript`, `template`, `textarea`, `select`, `option`, effective `contenteditable` regions, and documents in `designMode` untouched. Visible form labels, validation messages, and button text are converted, while text controls and selectable options are not. Textual DOM hidden by HTML, CSS, or `aria-hidden` is converted proactively so it is already Glagolitic if revealed. The implementation only changes text-node data and a reversible inline font fallback; it does not replace or wrap page elements. Disconnected nodes are restored and released after each mutation batch, while nodes moved and reconnected in the same batch keep their state.
|
||||||
|
|
||||||
@@ -39,10 +39,14 @@ The mapping uses dedicated Glagolitic letters for `Ё` (Yo), `Й` (I), `Щ` (Sht
|
|||||||
|
|
||||||
## Bundled fonts
|
## Bundled fonts
|
||||||
|
|
||||||
Glagolify bundles two OFL-licensed fonts so converted characters remain legible and the popup can switch between historical letter shapes:
|
The four selectable styles use two OFL-licensed typefaces:
|
||||||
|
|
||||||
- **Round:** [Noto Sans Glagolitic Regular](https://github.com/google/fonts/tree/main/ofl/notosansglagolitic), the standard rounded Unicode Glagolitic design. Its license is `fonts/OFL.txt`.
|
- **Round:** [Shafarik Regular](https://github.com/slavonic/Shafarik)'s standard Unicode Glagolitic glyphs. Its documentation identifies this character set as Round Glagolitic (`Okrugla glagoljica`), modeled on Bulgarian manuscript handwriting from the late 10th through early 12th centuries.
|
||||||
- **Angular:** [Shafarik Regular](https://github.com/google/fonts/tree/main/ofl/shafarik) with OpenType Stylistic Set 3 (`ss03`), which Shafarik defines as Croatian Angular or Square Glagolitic. Its license is `fonts/Shafarik-OFL.txt`.
|
- **Angular Sans:** [Noto Sans Glagolitic Regular](https://github.com/google/fonts/tree/main/ofl/notosansglagolitic), a modern sans design with angular letter construction.
|
||||||
|
- **Historical Angular:** Shafarik's OpenType Stylistic Set 3 (`ss03`), identified in its documentation as Croatian Angular or Square Glagolitic.
|
||||||
|
- **Missal Angular:** Shafarik's OpenType Stylistic Set 4 (`ss04`), modeled on Glagolitic used in 15th- and 16th-century printed missals from Kosinj and Venice.
|
||||||
|
|
||||||
|
Shafarik's license is `fonts/Shafarik-OFL.txt`; Noto Sans Glagolitic's license is `fonts/OFL.txt`. The three Shafarik faces use the CSS `size-adjust` font descriptor at 115% to compensate for the typeface's small lowercase Glagolitic body. Noto Sans Glagolitic retains its native metrics.
|
||||||
|
|
||||||
The selected font is inserted before each affected element's existing computed font family, so non-Glagolitic characters continue to use the page's own typography. If the page genuinely changes an affected element's inline `font-family` while Glagolify is enabled, the extension retains that latest page-owned declaration, reapplies the selected Glagolitic fallback, and restores the latest page value when switched off.
|
The selected font is inserted before each affected element's existing computed font family, so non-Glagolitic characters continue to use the page's own typography. If the page genuinely changes an affected element's inline `font-family` while Glagolify is enabled, the extension retains that latest page-owned declaration, reapplies the selected Glagolitic fallback, and restores the latest page value when switched off.
|
||||||
|
|
||||||
@@ -54,4 +58,4 @@ No install or build step is required. With Node.js available, run:
|
|||||||
npm test
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
The test suite validates the complete 66-entry mapping, mixed-content preservation, case, already-Glagolitic behavior, authoritative per-tab/background frame inheritance, and persisted round/angular font selection using Node's built-in test runner.
|
The test suite validates the complete 66-entry mapping, mixed-content preservation, case, already-Glagolitic behavior, authoritative per-tab/background frame inheritance, and persisted four-style font selection using Node's built-in test runner.
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const FONT_STYLE_KEY = "glagolifyFontStyle";
|
const FONT_STYLE_KEY = "glagolifyFontStyle";
|
||||||
const FONT_STYLES = new Set(["round", "angular"]);
|
const FONT_STYLES = new Set(["round", "angular-sans", "angular", "missal-angular"]);
|
||||||
const DEFAULT_FONT_STYLE = "round";
|
const DEFAULT_FONT_STYLE = "round";
|
||||||
|
|
||||||
function createBackgroundController(api) {
|
function createBackgroundController(api) {
|
||||||
|
|||||||
+22
@@ -1,3 +1,13 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "Glagolify Shafarik Round";
|
||||||
|
src: url("fonts/Shafarik-Regular.ttf") format("truetype");
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
size-adjust: 115%;
|
||||||
|
font-display: swap;
|
||||||
|
unicode-range: U+2C00-2C5F;
|
||||||
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Glagolify Noto Sans Glagolitic";
|
font-family: "Glagolify Noto Sans Glagolitic";
|
||||||
src: url("fonts/NotoSansGlagolitic-Regular.ttf") format("truetype");
|
src: url("fonts/NotoSansGlagolitic-Regular.ttf") format("truetype");
|
||||||
@@ -12,7 +22,19 @@
|
|||||||
src: url("fonts/Shafarik-Regular.ttf") format("truetype");
|
src: url("fonts/Shafarik-Regular.ttf") format("truetype");
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
size-adjust: 115%;
|
||||||
font-display: swap;
|
font-display: swap;
|
||||||
font-feature-settings: "ss03";
|
font-feature-settings: "ss03";
|
||||||
unicode-range: U+2C00-2C5F;
|
unicode-range: U+2C00-2C5F;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Glagolify Shafarik Missal Angular";
|
||||||
|
src: url("fonts/Shafarik-Regular.ttf") format("truetype");
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
size-adjust: 115%;
|
||||||
|
font-display: swap;
|
||||||
|
font-feature-settings: "ss04";
|
||||||
|
unicode-range: U+2C00-2C5F;
|
||||||
|
}
|
||||||
|
|||||||
+4
-2
@@ -3,8 +3,10 @@
|
|||||||
|
|
||||||
const EXCLUDED_ANCESTORS = "head, script, style, noscript, template, textarea, select, option";
|
const EXCLUDED_ANCESTORS = "head, script, style, noscript, template, textarea, select, option";
|
||||||
const FONT_FAMILIES = Object.freeze({
|
const FONT_FAMILIES = Object.freeze({
|
||||||
round: '"Glagolify Noto Sans Glagolitic"',
|
round: '"Glagolify Shafarik Round"',
|
||||||
angular: '"Glagolify Shafarik Angular"'
|
"angular-sans": '"Glagolify Noto Sans Glagolitic"',
|
||||||
|
angular: '"Glagolify Shafarik Angular"',
|
||||||
|
"missal-angular": '"Glagolify Shafarik Missal Angular"'
|
||||||
});
|
});
|
||||||
let fontFamily = FONT_FAMILIES.round;
|
let fontFamily = FONT_FAMILIES.round;
|
||||||
const trackedText = new Map();
|
const trackedText = new Map();
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "Glagolify Shafarik Round";
|
||||||
|
src: url("fonts/Shafarik-Regular.ttf") format("truetype");
|
||||||
|
size-adjust: 115%;
|
||||||
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Glagolify Noto Sans Glagolitic";
|
font-family: "Glagolify Noto Sans Glagolitic";
|
||||||
src: url("fonts/NotoSansGlagolitic-Regular.ttf") format("truetype");
|
src: url("fonts/NotoSansGlagolitic-Regular.ttf") format("truetype");
|
||||||
@@ -6,9 +12,17 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Glagolify Shafarik Angular";
|
font-family: "Glagolify Shafarik Angular";
|
||||||
src: url("fonts/Shafarik-Regular.ttf") format("truetype");
|
src: url("fonts/Shafarik-Regular.ttf") format("truetype");
|
||||||
|
size-adjust: 115%;
|
||||||
font-feature-settings: "ss03";
|
font-feature-settings: "ss03";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Glagolify Shafarik Missal Angular";
|
||||||
|
src: url("fonts/Shafarik-Regular.ttf") format("truetype");
|
||||||
|
size-adjust: 115%;
|
||||||
|
font-feature-settings: "ss04";
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
font: 14px/1.45 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
font: 14px/1.45 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
@@ -19,7 +33,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
width: 292px;
|
width: 320px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #22202b;
|
color: #22202b;
|
||||||
background: #fbfaff;
|
background: #fbfaff;
|
||||||
@@ -75,12 +89,13 @@ header p {
|
|||||||
|
|
||||||
.style-options {
|
.style-options {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.style-option {
|
.style-option {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.style-option input {
|
.style-option input {
|
||||||
@@ -90,10 +105,11 @@ header p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.style-option > span {
|
.style-option > span {
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
gap: 8px;
|
||||||
min-height: 38px;
|
min-height: 56px;
|
||||||
border: 1px solid #d9d2e5;
|
border: 1px solid #d9d2e5;
|
||||||
border-radius: 9px;
|
border-radius: 9px;
|
||||||
padding: 7px 9px;
|
padding: 7px 9px;
|
||||||
@@ -103,6 +119,11 @@ header p {
|
|||||||
transition: border-color 120ms ease, background 120ms ease, color 120ms ease;
|
transition: border-color 120ms ease, background 120ms ease, color 120ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.style-option > span > span:first-child {
|
||||||
|
min-width: 0;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
.style-option input:checked + span {
|
.style-option input:checked + span {
|
||||||
border-color: #6842b8;
|
border-color: #6842b8;
|
||||||
background: #eee7fb;
|
background: #eee7fb;
|
||||||
@@ -122,9 +143,14 @@ header p {
|
|||||||
.glyph {
|
.glyph {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.round-glyph {
|
.round-glyph {
|
||||||
|
font-family: "Glagolify Shafarik Round";
|
||||||
|
}
|
||||||
|
|
||||||
|
.angular-sans-glyph {
|
||||||
font-family: "Glagolify Noto Sans Glagolitic";
|
font-family: "Glagolify Noto Sans Glagolitic";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,6 +158,10 @@ header p {
|
|||||||
font-family: "Glagolify Shafarik Angular";
|
font-family: "Glagolify Shafarik Angular";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.missal-angular-glyph {
|
||||||
|
font-family: "Glagolify Shafarik Missal Angular";
|
||||||
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
min-height: 20px;
|
min-height: 20px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|||||||
+9
-1
@@ -23,9 +23,17 @@
|
|||||||
<input type="radio" name="font-style" value="round" checked disabled>
|
<input type="radio" name="font-style" value="round" checked disabled>
|
||||||
<span><span>Round</span><span class="glyph round-glyph">ⰀⰁ</span></span>
|
<span><span>Round</span><span class="glyph round-glyph">ⰀⰁ</span></span>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="style-option">
|
||||||
|
<input type="radio" name="font-style" value="angular-sans" disabled>
|
||||||
|
<span><span>Angular Sans</span><span class="glyph angular-sans-glyph">ⰀⰁ</span></span>
|
||||||
|
</label>
|
||||||
<label class="style-option">
|
<label class="style-option">
|
||||||
<input type="radio" name="font-style" value="angular" disabled>
|
<input type="radio" name="font-style" value="angular" disabled>
|
||||||
<span><span>Angular</span><span class="glyph angular-glyph">ⰀⰁ</span></span>
|
<span><span>Historical Angular</span><span class="glyph angular-glyph">ⰀⰁ</span></span>
|
||||||
|
</label>
|
||||||
|
<label class="style-option">
|
||||||
|
<input type="radio" name="font-style" value="missal-angular" disabled>
|
||||||
|
<span><span>Missal Angular</span><span class="glyph missal-angular-glyph">ⰀⰁ</span></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
const status = document.getElementById("status");
|
const status = document.getElementById("status");
|
||||||
const toggle = document.getElementById("toggle");
|
const toggle = document.getElementById("toggle");
|
||||||
const fontStyleInputs = [...document.querySelectorAll('input[name="font-style"]')];
|
const fontStyleInputs = [...document.querySelectorAll('input[name="font-style"]')];
|
||||||
|
const fontStyles = new Set(fontStyleInputs.map((input) => input.value));
|
||||||
let activeTabId = null;
|
let activeTabId = null;
|
||||||
let currentEnabled = false;
|
let currentEnabled = false;
|
||||||
let currentFontStyle = "round";
|
let currentFontStyle = "round";
|
||||||
@@ -25,7 +26,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderFontStyle(style) {
|
function renderFontStyle(style) {
|
||||||
currentFontStyle = style === "angular" ? "angular" : "round";
|
currentFontStyle = fontStyles.has(style) ? style : "round";
|
||||||
for (const input of fontStyleInputs) {
|
for (const input of fontStyleInputs) {
|
||||||
input.checked = input.value === currentFontStyle;
|
input.checked = input.value === currentFontStyle;
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-20
@@ -114,41 +114,43 @@ test("keeps states isolated between tabs and reports protected tabs", async () =
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("persists the font style and updates only enabled tabs", async () => {
|
test("persists every font style and updates only enabled tabs", async () => {
|
||||||
const fake = makeBrowser({ glagolifyFontStyle: "angular" });
|
const fake = makeBrowser({ glagolifyFontStyle: "missal-angular" });
|
||||||
const controller = createBackgroundController(fake.api);
|
const controller = createBackgroundController(fake.api);
|
||||||
|
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
await fake.send({ type: "glagolify:document-ready" }, { tab: { id: 7 } }),
|
await fake.send({ type: "glagolify:document-ready" }, { tab: { id: 7 } }),
|
||||||
{ enabled: false, fontStyle: "angular" }
|
{ enabled: false, fontStyle: "missal-angular" }
|
||||||
);
|
);
|
||||||
|
|
||||||
await fake.send({ type: "glagolify:set-tab-enabled", tabId: 7, enabled: true });
|
await fake.send({ type: "glagolify:set-tab-enabled", tabId: 7, enabled: true });
|
||||||
await fake.send({ type: "glagolify:set-tab-enabled", tabId: 8, enabled: false });
|
await fake.send({ type: "glagolify:set-tab-enabled", tabId: 8, enabled: false });
|
||||||
const broadcastCount = fake.broadcasts.length;
|
|
||||||
|
|
||||||
assert.deepEqual(
|
for (const fontStyle of ["round", "angular-sans", "angular", "missal-angular"]) {
|
||||||
await fake.send({ type: "glagolify:set-font-style", fontStyle: "round" }),
|
const broadcastCount = fake.broadcasts.length;
|
||||||
{ fontStyle: "round", accepted: true }
|
assert.deepEqual(
|
||||||
);
|
await fake.send({ type: "glagolify:set-font-style", fontStyle }),
|
||||||
assert.deepEqual(fake.storageWrites.at(-1), { glagolifyFontStyle: "round" });
|
{ fontStyle, accepted: true }
|
||||||
assert.equal(controller.getFontStyle(), "round");
|
);
|
||||||
assert.deepEqual(fake.broadcasts.slice(broadcastCount), [
|
assert.deepEqual(fake.storageWrites.at(-1), { glagolifyFontStyle: fontStyle });
|
||||||
{
|
assert.equal(controller.getFontStyle(), fontStyle);
|
||||||
tabId: 7,
|
assert.deepEqual(fake.broadcasts.slice(broadcastCount), [
|
||||||
message: {
|
{
|
||||||
type: "glagolify:set-enabled",
|
tabId: 7,
|
||||||
enabled: true,
|
message: {
|
||||||
fontStyle: "round"
|
type: "glagolify:set-enabled",
|
||||||
|
enabled: true,
|
||||||
|
fontStyle
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
]);
|
||||||
]);
|
}
|
||||||
|
|
||||||
const writesBeforeRejection = fake.storageWrites.length;
|
const writesBeforeRejection = fake.storageWrites.length;
|
||||||
const broadcastsBeforeRejection = fake.broadcasts.length;
|
const broadcastsBeforeRejection = fake.broadcasts.length;
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
await fake.send({ type: "glagolify:set-font-style", fontStyle: "italic" }),
|
await fake.send({ type: "glagolify:set-font-style", fontStyle: "italic" }),
|
||||||
{ fontStyle: "round", accepted: false }
|
{ fontStyle: "missal-angular", accepted: false }
|
||||||
);
|
);
|
||||||
assert.equal(fake.storageWrites.length, writesBeforeRejection);
|
assert.equal(fake.storageWrites.length, writesBeforeRejection);
|
||||||
assert.equal(fake.broadcasts.length, broadcastsBeforeRejection);
|
assert.equal(fake.broadcasts.length, broadcastsBeforeRejection);
|
||||||
|
|||||||
Reference in New Issue
Block a user