Skip to content

Commit

Permalink
Fixed bug breaking font optimization of italics; improves positioning…
Browse files Browse the repository at this point in the history
… calculations; added new tests
  • Loading branch information
Balearica committed Sep 2, 2024
1 parent 2977fd5 commit 823361e
Show file tree
Hide file tree
Showing 11 changed files with 81 additions and 39 deletions.
1 change: 0 additions & 1 deletion js/containers/imageContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,6 @@ export class ImageCache {
if (renderTransform) {
return ImageCache.transformImage(img1, n, props, true);
}
console.assert(nativeOnly, 'Binary should not be null when binary is needed');
return { native: img1, binary: null };
})();

Expand Down
4 changes: 2 additions & 2 deletions js/fontContainerMain.js
Original file line number Diff line number Diff line change
Expand Up @@ -348,10 +348,10 @@ export async function optimizeFontContainerFamily(fontFamily, fontMetricsObj) {
return new FontContainerFont(fontFamily.normal.family, fontFamily.normal.style, x.fontData, true, font);
});

const metricsItalic = fontMetricsObj[fontMetricsType][fontFamily.italic.style] && fontMetricsObj[fontMetricsType][fontFamily.italic.style].obs >= 200;
const metricsItalic = fontMetricsObj[fontMetricsType][fontFamily.italic.style];
/** @type {?FontContainerFont|Promise<FontContainerFont>} */
let italicOptFont = null;
if (metricsItalic) {
if (metricsItalic && metricsItalic.obs >= 200) {
italicOptFont = gs.scheduler.optimizeFont({ fontData: fontFamily.italic.src, fontMetricsObj: metricsItalic, style: fontFamily.italic.style })
.then(async (x) => {
const font = await loadOpentype(x.fontData, x.kerningPairs);
Expand Down
15 changes: 3 additions & 12 deletions js/worker/compareOCRModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,6 @@ export async function evalWords({
const useABaseline = options?.useABaseline === undefined ? true : options?.useABaseline;

const cosAngle = Math.cos(angle * -1 * (Math.PI / 180)) || 1;
const sinAngle = Math.sin(angle * -1 * (Math.PI / 180)) || 0;

// All words are assumed to be on the same line
const linebox = wordsA[0].line.bbox;
Expand Down Expand Up @@ -239,15 +238,12 @@ export async function evalWords({

// Draw the words in wordsA
let x0 = wordsA[0].bbox.left;
let y0 = linebox.bottom + baselineA[1] + baselineA[0] * (wordsA[0].bbox.left - linebox.left);
const y0 = linebox.bottom + baselineA[1] + baselineA[0] * (wordsA[0].bbox.left - linebox.left);
for (let i = 0; i < wordsA.length; i++) {
const word = wordsA[i];
const wordIBox = word.bbox;
const baselineY = linebox.bottom + baselineA[1] + baselineA[0] * (wordIBox.left - linebox.left);
const x = wordIBox.left;
const y = word.sup || word.dropcap ? wordIBox.bottom : baselineY;

const offsetX = (x - x0) * cosAngle - sinAngle * (y - y0);
const offsetX = (wordIBox.left - x0) / cosAngle;

await drawWordRender(calcCtx, word, offsetX, cropY, ctxView, Boolean(angle));
}
Expand Down Expand Up @@ -298,15 +294,10 @@ export async function evalWords({
// Set style to whatever it is for wordsA. This is based on the assumption that "A" is Tesseract Legacy and "B" is Tesseract LSTM (which does not have useful style info).
word.style = wordsA[0].style;

const baselineY = linebox.bottom + baselineB[1] + baselineB[0] * (word.bbox.left - linebox.left);
if (i === 0) {
x0 = word.bbox.left;
y0 = baselineY;
}
const x = word.bbox.left;
const y = word.sup || word.dropcap ? word.bbox.bottom : baselineY;

const offsetX = (x - x0) * cosAngle - sinAngle * (y - y0);
const offsetX = (word.bbox.left - x0) / cosAngle;

await drawWordRender(calcCtx, word, offsetX, cropY, ctxView, Boolean(angle));
}
Expand Down
31 changes: 8 additions & 23 deletions js/worker/renderWordCanvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// This differs from the code that renders to the main viewer canvas, which uses Konva.js.

import { FontCont } from '../containers/fontContainer.js';
import ocr from '../objects/ocrObjects.js';
import { calcLineFontSize, calcWordMetrics } from '../utils/fontUtils.js';

/**
Expand Down Expand Up @@ -42,9 +43,6 @@ export async function drawWordActual(ctx, words, imageBinaryBit, imgDims, angle,
const sinAngle = Math.sin(angle * (Math.PI / 180));
const cosAngle = Math.cos(angle * (Math.PI / 180));

const shiftX = sinAngle * (imgDims.height * 0.5) * -1 || 0;
const shiftY = sinAngle * ((imgDims.width - shiftX) * 0.5) || 0;

const wordsBox = words.map((x) => x.bbox);

// Union of all bounding boxes
Expand All @@ -56,46 +54,33 @@ export async function drawWordActual(ctx, words, imageBinaryBit, imgDims, angle,
};

// All words are assumed to be on the same line
const lineObj = words[0].line;
const linebox = words[0].line.bbox;
const { baseline } = words[0].line;

let angleAdjXLine = 0;
let angleAdjYLine = 0;
if (Math.abs(angle ?? 0) > 0.05) {
const x = linebox.left;
const y = linebox.bottom + baseline[1];

const xRot = x * cosAngle - sinAngle * y;
// const yRot = x * sinAngle + cosAngle * y;

const angleAdjXInt = x - xRot;

const angleAdjYInt = sinAngle * (linebox.left + angleAdjXInt / 2) * -1;

angleAdjXLine = angleAdjXInt + shiftX;
angleAdjYLine = angleAdjYInt + shiftY;
}
const imageRotated = angle !== 0;
const angleAdjLine = imageRotated ? ocr.calcLineStartAngleAdj(lineObj) : { x: 0, y: 0 };

const angleAdjXWord = Math.abs(angle) >= 1 ? angleAdjXLine + (1 - cosAngle) * (wordBoxUnion.left - linebox.left) : angleAdjXLine;
const start = linebox.left + angleAdjLine.x + (wordBoxUnion.left - linebox.left) / cosAngle;

// We crop to the dimensions of the font (fontAsc and fontDesc) rather than the image bounding box.
const height = fontAsc && fontDesc ? fontAsc + fontDesc : wordBoxUnion.bottom - wordBoxUnion.top + 1;
const width = wordBoxUnion.right - wordBoxUnion.left + 1;

const cropY = linebox.bottom + baseline[1] - fontAsc - 1;
const cropYAdj = cropY + angleAdjYLine;
const cropYAdj = cropY + angleAdjLine.y;

ctx.canvas.height = height;
ctx.canvas.width = width;

ctx.drawImage(imageBinaryBit, wordBoxUnion.left + angleAdjXWord - 1, cropYAdj, width, height, 0, 0, width, height);
ctx.drawImage(imageBinaryBit, start - 1, cropYAdj, width, height, 0, 0, width, height);

if (ctxViewArr && ctxViewArr.length > 0) {
for (let i = 0; i < ctxViewArr.length; i++) {
const ctxI = ctxViewArr[i];
ctxI.canvas.height = height;
ctxI.canvas.width = width;
ctxI.drawImage(imageBinaryBit, wordBoxUnion.left + angleAdjXWord - 1, cropYAdj, width, height, 0, 0, width, height);
ctxI.drawImage(imageBinaryBit, start - 1, cropYAdj, width, height, 0, 0, width, height);
}
}

Expand Down
Binary file added tests/assets/drop_cap_2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/drop_cap_2_rot5.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/drop_cap_2_rotc5.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/simple_paragraph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/simple_paragraph_rot5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/assets/simple_paragraph_rotc5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
69 changes: 68 additions & 1 deletion tests/module/recognize.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('Check basic recognition features.', function () {
}).timeout(10000);
});

describe('Check recognition-related features.', function () {
describe('Check font optimization features.', function () {
this.timeout(20000);
before(async () => {
// For this input image, font optimization significantly improves overlap quality.
Expand All @@ -51,3 +51,70 @@ describe('Check recognition-related features.', function () {
await scribe.terminate();
});
});

describe('Check that font optimization works with italics.', function () {
this.timeout(20000);
before(async () => {
// This article page contains mostly italic text.
await scribe.importFiles([`${ASSETS_PATH_KARMA}/article_italics.png`]);
await scribe.recognize({
modeAdv: 'legacy',
});
});

it('Font optimization improves overlap quality with italics', async () => {
if (!scribe.data.font.rawMetrics) throw new Error('DebugData.evalRaw is not defined');
if (!scribe.data.font.optMetrics) throw new Error('DebugData.evalOpt is not defined');
assert.isBelow(scribe.data.font.optMetrics.Palatino, scribe.data.font.rawMetrics.Palatino);
assert.isBelow(scribe.data.font.optMetrics.Palatino, 0.35);
}).timeout(10000);

it('Font optimization should be enabled when it improves overlap quality', async () => {
assert.strictEqual(scribe.data.font.enableOpt, true);
}).timeout(10000);

after(async () => {
await scribe.terminate();
});
});

describe('Check auto-rotate features.', function () {
this.timeout(20000);

it('Baseline overlap is decent', async () => {
await scribe.importFiles([`${ASSETS_PATH_KARMA}/simple_paragraph.png`]);
await scribe.recognize({
modeAdv: 'legacy',
});

if (!scribe.data.font.rawMetrics) throw new Error('DebugData.evalRaw is not defined');
if (!scribe.data.font.optMetrics) throw new Error('DebugData.evalOpt is not defined');
assert.isBelow(scribe.data.font.optMetrics.NimbusRomNo9L, 0.4);
}).timeout(10000);

it('Overlap with clockwise rotation is decent', async () => {
await scribe.importFiles([`${ASSETS_PATH_KARMA}/simple_paragraph_rot5.png`]);
await scribe.recognize({
modeAdv: 'legacy',
});

if (!scribe.data.font.rawMetrics) throw new Error('DebugData.evalRaw is not defined');
if (!scribe.data.font.optMetrics) throw new Error('DebugData.evalOpt is not defined');
assert.isBelow(scribe.data.font.optMetrics.NimbusRomNo9L, 0.4);
}).timeout(10000);

it('Overlap with counterclockwise rotation is decent', async () => {
await scribe.importFiles([`${ASSETS_PATH_KARMA}/simple_paragraph_rotc5.png`]);
await scribe.recognize({
modeAdv: 'legacy',
});

if (!scribe.data.font.rawMetrics) throw new Error('DebugData.evalRaw is not defined');
if (!scribe.data.font.optMetrics) throw new Error('DebugData.evalOpt is not defined');
assert.isBelow(scribe.data.font.optMetrics.NimbusRomNo9L, 0.4);
}).timeout(10000);

after(async () => {
await scribe.terminate();
});
});

0 comments on commit 823361e

Please sign in to comment.