Skip to content

Commit

Permalink
Move general font methods into Font class.
Browse files Browse the repository at this point in the history
Methods remaining in TextFont deal with formatting / metrics.
TextFont will be renamed to TextFormatter.
  • Loading branch information
ronyeh committed Nov 4, 2021
1 parent 2d400a5 commit 978ed44
Show file tree
Hide file tree
Showing 24 changed files with 401 additions and 376 deletions.
22 changes: 22 additions & 0 deletions CHANGELOG.fonts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 4.0.0 / fonts

## Breaking

- `TextFontMetrics` has been merged into `FontGlyph` due to substantial overlap.
- xxxx
- xxxx
- xxxx
- xxxx
- xxxx
- xxxx
- xxxx

## Renamed

```
NEW NAME OLD NAME
------------------------------------------------------------------------
Flow.NOTATION_FONT_SCALE <= Flow.DEFAULT_NOTATION_FONT_SCALE
StaveNote.LEDGER_LINE_OFFSET <= StaveNote.DEFAULT_LEDGER_LINE_OFFSET
ChordSymbol.metrics <= ChordSymbol.chordSymbolMetrics
```
3 changes: 2 additions & 1 deletion src/annotation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Modifier } from './modifier';
import { ModifierContextState } from './modifiercontext';
import { StemmableNote } from './stemmablenote';
import { Tables } from './tables';
import { TextFormatter } from './textformatter';
import { log } from './util';

// eslint-disable-next-line
Expand Down Expand Up @@ -79,7 +80,7 @@ export class Annotation extends Modifier {
// Calculate if the vertical extent will exceed a single line and adjust accordingly.
const numLines = Math.floor(textFormatter.maxHeight / Tables.STAVE_LINE_DISTANCE) + 1;
// Get the string width from the font metrics
testWidth = textFormatter.getWidthForString(annotation.text);
testWidth = textFormatter.getWidthForText(annotation.text);
width = Math.max(width, testWidth);
if (annotation.getPosition() === Modifier.Position.ABOVE) {
annotation.setTextLine(state.top_text_line);
Expand Down
5 changes: 2 additions & 3 deletions src/bend.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
// MIT License

import { Element } from 'element';
import { FontInfo } from 'types/common';

import { Element } from './element';
import { FontInfo } from './font';
import { Modifier } from './modifier';
import { ModifierContextState } from './modifiercontext';
import { Tables } from './tables';
Expand Down
46 changes: 25 additions & 21 deletions src/chordsymbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
//
// See `tests/chordsymbol_tests.ts` for usage examples.

import { Font, FontInfo } from './font';
import { Font, FontInfo, FontStyle, FontWeight } from './font';
import { Glyph } from './glyph';
import { Modifier } from './modifier';
import { ModifierContextState } from './modifiercontext';
import { StemmableNote } from './stemmablenote';
import { Tables } from './tables';
import { FontStyle, FontWeight, TextFont } from './textfont';
import { TextFormatter } from './textformatter';
import { log } from './util';

// To enable logging for this class. Set `Vex.Flow.ChordSymbol.DEBUG` to `true`.
Expand Down Expand Up @@ -90,7 +90,7 @@ export class ChordSymbol extends Modifier {
};

static get superSubRatio(): number {
return ChordSymbol.chordSymbolMetrics.global.superSubRatio;
return ChordSymbol.metrics.global.superSubRatio;
}

/** Currently unused: Globally turn off text formatting, if the built-in formatting does not work for your font. */
Expand All @@ -104,8 +104,8 @@ export class ChordSymbol extends Modifier {

// eslint-disable-next-line
static getMetricForGlyph(glyphCode: string): any {
if (ChordSymbol.chordSymbolMetrics[glyphCode]) {
return ChordSymbol.chordSymbolMetrics[glyphCode];
if (ChordSymbol.metrics[glyphCode]) {
return ChordSymbol.metrics[glyphCode];
}
return undefined;
}
Expand All @@ -115,7 +115,7 @@ export class ChordSymbol extends Modifier {
}

static get spacingBetweenBlocks(): number {
return ChordSymbol.chordSymbolMetrics.global.spacing / ChordSymbol.engravingFontResolution;
return ChordSymbol.metrics.global.spacing / ChordSymbol.engravingFontResolution;
}

static getWidthForGlyph(glyph: Glyph): number {
Expand Down Expand Up @@ -143,15 +143,15 @@ export class ChordSymbol extends Modifier {
}

static get superscriptOffset(): number {
return ChordSymbol.chordSymbolMetrics.global.superscriptOffset / ChordSymbol.engravingFontResolution;
return ChordSymbol.metrics.global.superscriptOffset / ChordSymbol.engravingFontResolution;
}

static get subscriptOffset(): number {
return ChordSymbol.chordSymbolMetrics.global.subscriptOffset / ChordSymbol.engravingFontResolution;
return ChordSymbol.metrics.global.subscriptOffset / ChordSymbol.engravingFontResolution;
}

static get kerningOffset(): number {
return ChordSymbol.chordSymbolMetrics.global.kerningOffset / ChordSymbol.engravingFontResolution;
return ChordSymbol.metrics.global.kerningOffset / ChordSymbol.engravingFontResolution;
}

// Glyph data
Expand Down Expand Up @@ -223,16 +223,19 @@ export class ChordSymbol extends Modifier {
static readonly symbolModifiers = SymbolModifiers;

// eslint-disable-next-line
static get chordSymbolMetrics(): any {
static get metrics(): any {
return Tables.MUSIC_FONT_STACK[0].getMetrics().glyphs.chordSymbol;
}

static get lowerKerningText(): string[] {
return Tables.MUSIC_FONT_STACK[0].getMetrics().glyphs.chordSymbol.global.lowerKerningText;
// For example, see: `bravura_metrics.ts`
// BravuraMetrics.glyphs.chordSymbol.global.lowerKerningText, which returns an array of letters.
// ['D', 'F', 'P', 'T', 'V', 'Y']
return ChordSymbol.metrics.global.lowerKerningText;
}

static get upperKerningText(): string[] {
return Tables.MUSIC_FONT_STACK[0].getMetrics().glyphs.chordSymbol.global.upperKerningText;
return ChordSymbol.metrics.global.upperKerningText;
}

static isSuperscript(block: ChordSymbolBlock): boolean {
Expand All @@ -257,8 +260,8 @@ export class ChordSymbol extends Modifier {
for (const symbol of symbols) {
// symbol.font was initialized by the constructor via this.setFont().
// eslint-disable-next-line
const fontSize = TextFont.convertSizeToNumber(symbol.font!.size);
const fontAdj = TextFont.scaleSize(fontSize, 0.05);
const fontSize = Font.convertSizeToNumber(symbol.font!.size);
const fontAdj = Font.scaleSize(fontSize, 0.05);
const glyphAdj = fontAdj * 2;
let lineSpaces = 1;
let vAlign = false;
Expand All @@ -278,7 +281,7 @@ export class ChordSymbol extends Modifier {

// If there is a symbol-specific offset, add it but consider font
// size since font and glyphs will be interspersed.
const fontSize = symbol.textFormatter.sizeInPixels;
const fontSize = symbol.textFormatter.fontSizeInPx;
const superSubFontSize = fontSize * superSubScale;
if (block.symbolType === SymbolTypes.GLYPH && block.glyph !== undefined) {
block.width = ChordSymbol.getWidthForGlyph(block.glyph) * superSubFontSize;
Expand Down Expand Up @@ -388,11 +391,11 @@ export class ChordSymbol extends Modifier {
*/

get superscriptOffset(): number {
return ChordSymbol.superscriptOffset * this.textFormatter.sizeInPixels;
return ChordSymbol.superscriptOffset * this.textFormatter.fontSizeInPx;
}

get subscriptOffset(): number {
return ChordSymbol.subscriptOffset * this.textFormatter.sizeInPixels;
return ChordSymbol.subscriptOffset * this.textFormatter.fontSizeInPx;
}

setReportWidth(value: boolean): this {
Expand All @@ -415,7 +418,7 @@ export class ChordSymbol extends Modifier {
}
const bar = this.symbolBlocks[barIndex];
const xoff = bar.width / 4;
const yoff = 0.25 * this.textFormatter.sizeInPixels;
const yoff = 0.25 * this.textFormatter.fontSizeInPx;
let symIndex = 0;
for (symIndex === 0; symIndex < barIndex; ++symIndex) {
const symbol = this.symbolBlocks[symIndex];
Expand Down Expand Up @@ -474,7 +477,7 @@ export class ChordSymbol extends Modifier {
preKernLower = ChordSymbol.lowerKerningText.some((xx) => xx === prevSymbol.text[prevSymbol.text.length - 1]);
}

const kerningOffsetPixels = ChordSymbol.kerningOffset * this.textFormatter.sizeInPixels;
const kerningOffsetPixels = ChordSymbol.kerningOffset * this.textFormatter.fontSizeInPx;
// TODO: adjust kern for font size.
// Where should this constant live?
if (preKernUpper && currSymbol.symbolModifier === SymbolModifiers.SUPERSCRIPT) {
Expand Down Expand Up @@ -673,7 +676,8 @@ export class ChordSymbol extends Modifier {
for (i = 0; i < text.length; ++i) {
const metric = this.textFormatter.getMetricForCharacter(text[i]);
if (metric) {
acc = metric.y_max < acc ? metric.y_max : acc;
const yMax = metric.y_max ?? 0;
acc = yMax < acc ? yMax : acc;
}
}

Expand Down Expand Up @@ -753,7 +757,7 @@ export class ChordSymbol extends Modifier {
ctx.save();
if (this.font) {
const { family, size, weight, style } = this.font;
const smallerFontSize = TextFont.scaleSize(size, ChordSymbol.superSubRatio);
const smallerFontSize = Font.scaleSize(size, ChordSymbol.superSubRatio);
ctx.setFont(family, smallerFontSize, weight, style);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ export class Factory {
};
// TODO: Factory.Annotation has a different default font from new Annotation()...
const font = {
family: 'Times' /* RONYEH: TextFont.SERIF */,
family: 'Times' /* RONYEH: Font.SERIF */,
size: 14,
weight: FontWeight.BOLD,
style: FontStyle.ITALIC,
Expand Down
2 changes: 1 addition & 1 deletion src/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export const Flow = {
TabTie,
TextBracket,
TextDynamics,
TextFont,
TextFont, // TODO: RONYEH rename to FontFormatter
TextNote,
TickContext,
TimeSignature,
Expand Down
136 changes: 134 additions & 2 deletions src/font.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,29 @@ export interface FontGlyph {
y_min?: number;
y_max?: number;
ha: number;
o: string;
leftSideBearing?: number;
advanceWidth?: number;
o?: string; // RONYEH-FONT: Made this optional to be compatible with robotoslab_textmetrics & petalumascript_textmetrics.
cached_outline?: number[];
}

export enum FontWeight {
NORMAL = 'normal',
BOLD = 'bold',
}

export enum FontStyle {
NORMAL = 'normal',
ITALIC = 'italic',
}

// Internal <span></span> element for parsing CSS font shorthand strings.
let fontParser: HTMLSpanElement;

class Font {
//////////////////////////////////////////////////////////////////////////////////////////////////
// Static Members

/** Default sans-serif font family. */
static SANS_SERIF: string = 'Arial, sans-serif';

Expand All @@ -57,13 +73,129 @@ class Font {
/** Default font size in `pt`. */
static SIZE: number = 10;

// CSS Font Sizes: 36pt == 48px == 3em == 300% == 0.5in
/** Given a length (for units: pt, px, em, %, in, mm, cm) what is the scale factor to convert it to px? */
static convertToPxScaleFactor: Record<string, number> = {
pt: 4 / 3,
px: 1,
em: 16,
'%': 4 / 25,
in: 96,
mm: 96 / 25.4,
cm: 96 / 2.54,
};

/**
* @param fontSize The font size to convert. Can be specified as a CSS length string (e.g., '16pt', '1em')
* or as a number (the unit is assumed to be 'pt'). See `Font.convertToPxScaleFactor` for the supported
* units (e.g., pt, em, %).
* @returns the number of pixels that is equivalent to `fontSize`
*/
static convertToPixels(fontSize: string | number = Font.SIZE): number {
if (typeof fontSize === 'number') {
// Assume the fontSize is specified in pt.
return (fontSize * 4) / 3;
} else {
const value = parseFloat(fontSize);
if (isNaN(value)) {
return 0;
}
const unit = fontSize.replace(/[\d.\s]/g, ''); // Remove all numbers, dots, spaces.
const conversionFactor = Font.convertToPxScaleFactor[unit] ?? 1;
return value * conversionFactor;
}
}

/**
* @param fontShorthand a string formatted as CSS font shorthand (e.g., 'italic bold 15pt Arial').
*/
static parseFont(fontShorthand: string): FontInfo {
if (!fontParser) {
fontParser = document.createElement('span');
}
fontParser.style.font = fontShorthand;
const { fontFamily, fontSize, fontWeight, fontStyle } = fontParser.style;
return { family: fontFamily, size: fontSize, weight: fontWeight, style: fontStyle };
}

/**
* @param fontSize a number representing a font size, or a string font size with units.
* @param scaleFactor multiply the size by this factor.
* @returns size * scaleFactor (e.g., 16pt * 3 = 48pt, 8px * 0.5 = 4px, 24 * 2 = 48)
*/
static scaleSize<T extends number | string>(fontSize: T, scaleFactor: number): T {
if (typeof fontSize === 'number') {
return (fontSize * scaleFactor) as T;
} else {
const value = parseFloat(fontSize);
const unit = fontSize.replace(/[\d.\s]/g, ''); // Remove all numbers, dots, spaces.
return `${value * scaleFactor}${unit}` as T;
}
}

static convertSizeToNumber(fontSize: number | string): number {
if (typeof fontSize === 'number') {
return fontSize;
} else {
return parseFloat(fontSize);
}
}

/**
* Helper for `TextFont.createFormatter()`.
* @param weight a string (e.g., 'bold') or a number (e.g., 600 / semi-bold in the OpenType spec).
* @returns true if the font weight indicates bold.
*/
static isBold(weight?: string | number): boolean {
if (!weight) {
return false;
} else if (typeof weight === 'number') {
return weight >= 600;
} else {
// a string can be 'bold' or '700'
const parsedWeight = parseInt(weight, 10);
if (isNaN(parsedWeight)) {
return weight.toLowerCase() === 'bold';
} else {
return parsedWeight >= 600;
}
}
}

/**
* Helper for `TextFont.createFormatter()`.
* @param style
* @returns true if the font style indicates 'italic'.
*/
static isItalic(style?: string): boolean {
if (!style) {
return false;
} else {
return style.toLowerCase() === FontStyle.ITALIC;
}
}

static loadDefaultWebFonts(): void {
//
console.log('loadDefaultWebFonts!!!');
}

static loadWebFont(): void {
//
// XXXX
console.log('loadWebFont YAY!!!');
}

//////////////////////////////////////////////////////////////////////////////////////////////////
// Instance Members

protected name: string;
protected fontDataMetrics: FontDataMetrics;

// eslint-disable-next-line
constructor(name: string, metrics?: Record<string, any>, fontData?: FontData) {
this.name = name;
this.fontDataMetrics = { fontData: undefined, metrics: undefined };
this.fontDataMetrics = {};
switch (name) {
case 'Bravura':
loadBravura(this.fontDataMetrics);
Expand Down
2 changes: 1 addition & 1 deletion src/fonts/petalumascript_textmetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -860,7 +860,7 @@ export const PetalumaScriptTextMetrics = {
},
},
fontFamily_RONYEH: 'PetalumaScript',
fontFamily: 'Petaluma Script' /* Need to remove the space! */,
fontFamily: 'PetalumaScript' /* RONYEH: Fixed by removing the space. */,
resolution: 1000,
generatedOn: '2020-06-14T18:33:25.407Z',
};
Loading

0 comments on commit 978ed44

Please sign in to comment.